Java多线程编程核心技术---学习分享
继承Thread类实现多线程
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread...");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束..");
}
}
运行结果如下:
运行结束..
MyThread...
实现Runnable接口实现多线程
如果创建的线程类已经有一个父类了,就不能再集成Thread类,因为Java不支持多继承,这时就需要实现Runnable接口。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable is running...");
}
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束..");
}
}
运行结果如下:
运行结束..
MyRunnable is running...
实例变量与线程安全
- 不共享数据的情况
public class MyThread3 extends Thread {
private int count = 5;
public MyThread3(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("执行者:" + this.currentThread().getName() + ",count=" + count);
}
}
public static void main(String[] args) {
MyThread3 t1 = new MyThread3("A");
MyThread3 t2 = new MyThread3("B");
MyThread3 t3 = new MyThread3("C");
t1.start();
t2.start();
t3.start();
}
}
某一次运行结果如下:
执行者:B,count=4
执行者:B,count=3
执行者:B,count=2
执行者:C,count=4
执行者:A,count=4
执行者:A,count=3
执行者:C,count=3
执行者:C,count=2
执行者:B,count=1
执行者:B,count=0
执行者:C,count=1
执行者:A,count=2
执行者:A,count=1
执行者:A,count=0
执行者:C,count=0
- 共享数据的情况
public class MyThread4 extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("执行者:" + Thread.currentThread().getName() + ",count=" + count);
}
public static void main(String[] args) {
MyThread4 myThread = new MyThread4();
Thread t1 = new Thread(myThread, "A");
Thread t2 = new Thread(myThread, "B");
Thread t3 = new Thread(myThread, "C");
Thread t4 = new Thread(myThread, "D");
Thread t5 = new Thread(myThread, "E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
某一次的运行结果如下:
执行者:A,count=3
执行者:D,count=1
执行者:E,count=2
执行者:B,count=3
执行者:C,count=0
i--分成如下三步:
取得原有的i值
计算i-1
对i进行赋值
synchronized同步方法
方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。
如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。
package com.umgsai.thread.thread73;
多个对象多个锁
public class Run {
public static void main(String[] args) {
Service service1 = new Service();
Service service2 = new Service();
ThreadA threadA = new ThreadA(service1);
threadA.start();
ThreadB threadB = new ThreadB(service2);
threadB.start();
}
}
上面的代码是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步方式运行的。synchronized关键字取得的锁是对象锁,而不是把一段代码或方法当做锁,所以在以上的代码中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有改方法所属对象的锁Lock,那么其他线程只能处于等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。
synchronized方法的弊端
同步方法里的长任务。
package com.umgsai.thread.thread74;
当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
package com.umgsai.thread.thread74;
synchronized同步方法
- 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
- 同一时间只有一个线程可以执行synchronized同步方法中的代码。
synchronized(this)同步代码块
- 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
- 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
数据类型String的常量池特性
package com.umgsai.thread.thread76;
出现这种情况就是因为Sting的两个值都是AA,两个线程持有相同的锁,所以造成线程B不能执行。因此在大多数情况下,同步synchronized代码块都不实用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但他并不放入缓存中。
同步synchronized方法无限等待与解决
public class Service {
synchronized public void methodA(){
System.out.println("methodA begin...");
boolean condition = true;
while (condition) {
}
System.out.println("methodA end...");
}
synchronized public void methodB(){
System.out.println("methodB begin...");
System.out.println("methodB end...");
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.methodA();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.methodB();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
ThreadB b = new ThreadB(service);
b.setName("B");
a.start();
b.start();
}
}
控制台打印结果如下:
methodA begin...
线程A处于死循环状态,线程B永远无法拿到Service对象锁而一直得不到运行。
对Service对象做如下修改:
public class Service {
Object object1 = new Object();
Object object2 = new Object();
public void methodA() {
synchronized (object1) {
System.out.println("methodA begin...");
boolean condition = true;
while (condition) {
}
System.out.println("methodA end...");
}
}
public void methodB() {
synchronized (object2) {
System.out.println("methodB begin...");
System.out.println("methodB end...");
}
}
}
此时控制台打印结果如下:
methodA begin...
methodB begin...
methodB end...
methodA()和methodB()对不同的对象加锁,所以线程A持有的锁不会对线程B造成影响。
多线程死锁
package com.umgsai.thread.thread17
public class DeadThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run() {
if (username.equals("a")) {
synchronized (lock1) {
try {
System.out.println("username=" + username);
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按lock1->lock2代码顺序执行了");
}
}
}
if (username.equals("b")) {
synchronized (lock2) {
try {
System.out.println("username=" + username);
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1代码顺序执行了");
}
}
}
}
public static void main(String[] args) {
try {
DeadThread t1 = new DeadThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(200);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以使用jps命令查看当前线程的id,然后使用jstack -l id来检查是否存在死锁。
对象锁的改变
package com.umgsai.thread.thread21;
public class MyService {
private String lock = "123";
public void testMethod() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
lock = "456";
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run1 {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
ThreadB b = new ThreadB(service);
b.setName("B");
a.start();
Thread.sleep(100);
b.start();
}
}
关键字volatile与死循环
package com.umgsai.thread.thread22
public class PrintString {
private boolean isContinuePrint = true;
public boolean isContinuePrint() {
return isContinuePrint;
}
public void setContinuePrint(boolean isContinuePrint) {
this.isContinuePrint = isContinuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint) {
System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
PrintString printString = new PrintString();
printString.printStringMethod();//同步执行,死循环
System.out.println("停止线程...");
printString.setContinuePrint(false);
}
}
以上同步代码出现死循环,无法停止
public class NewPrintString implements Runnable {
private boolean isContinuePrint = true;
public boolean isContinuePrint() {
return isContinuePrint;
}
public void setContinuePrint(boolean isContinuePrint) {
this.isContinuePrint = isContinuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint) {
System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
printStringMethod();
}
public static void main(String[] args) throws InterruptedException {
NewPrintString printString = new NewPrintString();
new Thread(printString).start();
Thread.sleep(5000);
System.out.println("停止线程...");
printString.setContinuePrint(false);
}
}
注:《Java多线程编程核心技术》P120讲将上面的代码运行在-server服务器模式中的64bit的JVM上时,会出现死循环。实际测试并未出现死循环,暂未弄清原因。
解决可能出现的死循环
public class RunThread extends Thread {
volatile private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入run方法...");
while (isRunning) {
System.out.println("running....");
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("线程被停止...");
}
public static void main(String[] args) {
try {
RunThread runThread = new RunThread();
runThread.start();
Thread.sleep(1000);
runThread.setRunning(false);
System.out.println("已将isRunning设置为false");
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用volatile关键字强制从公共内存中读取变量。
使用volatile关键字增加了实例变量在多个线程之间的可见性,但是volatile关键字不支持原子性。
synchronized和volatile的比较
- 关键字volatile是线程同步的轻量级实现,性能比synchronized好。volatile只能修饰变量,synchronized可以修饰方法和代码块。
- 多线程访问volatile不会发生阻塞,synchronized会出现阻塞。
- volatile能保证数据的可见性,但不能保证原子性。synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
- 关键字volatile解决的是变量在多个线程之间的可见性,synchronized解决的是多个线程之间访问资源的同步性。
volatile的非原子性
public class VolatileTest extends Thread {
volatile public static int count;
private static void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileTest[] volatileTests = new VolatileTest[100];
for (int i = 0; i < 100; i++) {
volatileTests[i] = new VolatileTest();
}
for (int i = 0; i < 100; i++) {
volatileTests[i].start();
}
}
}
控制台打印结果如下:
.......
count=5332
count=5232
count=5132
count=5032
count=4932
count=4854
count=4732
count=4732
使用synchronized关键字
·```java
public class VolatileTest extends Thread {
volatile public static int count;
//一定要加static关键字,这样synchronized与static锁的内容就是VolatileTest类了,也就达到同步效果了。
synchronized private static void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileTest[] volatileTests = new VolatileTest[100];
for (int i = 0; i < 100; i++) {
volatileTests[i] = new VolatileTest();
}
for (int i = 0; i < 100; i++) {
volatileTests[i].start();
}
}
}
此时控制台打印结果如下:
......
count=9300
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000
### 使用原子类进行i++操作
```java
package com.umgsai.thread.thread23;
public class AtomicIntegerTest extends Thread {
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(count.incrementAndGet());
}
}
public static void main(String[] args) {
AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
Thread t1 = new Thread(atomicIntegerTest);
Thread t2 = new Thread(atomicIntegerTest);
Thread t3 = new Thread(atomicIntegerTest);
Thread t4 = new Thread(atomicIntegerTest);
Thread t5 = new Thread(atomicIntegerTest);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
控制台打印结果如下:
......
4992
4993
4994
4995
4996
4997
4998
4999
5000
原子类也并不完全安全
package com.umgsai.thread.thread24
public class MyService {
public static AtomicLong atomicLong = new AtomicLong();
public void addNum() {
System.out.println(Thread.currentThread().getName() + " 加了100之后是:" + atomicLong.addAndGet(100));
atomicLong.addAndGet(1);
}
}
public class MyThread extends Thread {
private MyService myService;
public MyThread(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
myService.addNum();
}
}
public class Run {
public static void main(String[] args) {
try {
MyService myService = new MyService();
MyThread[] array = new MyThread[100];
for (int i = 0; i < array.length; i++) {
array[i] = new MyThread(myService);
}
for (int i = 0; i < array.length; i++) {
array[i].start();;
}
Thread.sleep(1000);
System.out.println(myService.atomicLong.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印结果如下:
......
Thread-89 加了100之后是:8987
Thread-96 加了100之后是:9493
Thread-80 加了100之后是:9594
Thread-94 加了100之后是:9695
Thread-97 加了100之后是:9796
Thread-95 加了100之后是:9896
Thread-98 加了100之后是:9998
Thread-99 加了100之后是:10098
10100
累加的结果是正确的,但是打印顺序的错的,这是因为虽然addAndGet方法是原子的,但是方法和方法之间的调用却不是原子的。
解决方法:加上synchronized即可。
关键字synchronized可以使多个线程访问同一个资源具有同步性,而且还可以将线程工作内存中的私有变量与公共内存中的变量进行同步。
package com.umgsai.thread.thread25;
public class Service {
private boolean isContinueRun = true;
public void runMethod() {
while (isContinueRun) {
}
System.out.println("stop...");
}
public void stopMethod() {
isContinueRun = false;
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.runMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.stopMethod();
}
}
public class Run {
public static void main(String[] args) {
try {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.start();
Thread.sleep(1000);
ThreadB b = new ThreadB(service);
b.start();
System.out.println("已经发起停止命令了");
} catch (Exception e) {
e.printStackTrace();
}
}
}
出现死循环。
对Service做如下修改
public class Service {
private boolean isContinueRun = true;
public void runMethod() {
String anyString = new String();
while (isContinueRun) {
synchronized (anyString) {
}
}
System.out.println("stop...");
}
public void stopMethod() {
isContinueRun = false;
}
}
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程都看到由同一个锁保护之前所有的修改结果。
Java多线程编程核心技术---学习分享的更多相关文章
- java多线程编程核心技术学习-1
实现多线程的两种方式 继承Thread类,重写Thread类中的run方法 public class MyThread extends Thread{ @Override public void ru ...
- 《java多线程编程核心技术》(一)使用多线程
了解多线程 进程和多线程的概念和线程的优点: 提及多线程技术,不得不提及"进程"这个概念.百度百科对"进程"的解释如下: 进程(Process)是计算机中的程序 ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
- Java多线程编程核心技术(二)对象及变量的并发访问
本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...
- Java多线程编程核心技术(一)Java多线程技能
1.进程和线程 一个程序就是一个进程,而一个程序中的多个任务则被称为线程. 进程是表示资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位. 举个例子: 打开你的计算机上的任务管 ...
- Java多线程编程核心技术---对象及变量的并发访问(二)
数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...
- Java多线程编程核心技术
Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...
- 《Java多线程编程核心技术》推荐
写这篇博客主要是给猿友们推荐一本书<Java多线程编程核心技术>. 之所以要推荐它,主要因为这本书写得十分通俗易懂,以实例贯穿整本书,使得原本抽象的概念,理解起来不再抽象. 只要你有一点点 ...
- 《Java 多线程编程核心技术》- 笔记
作为业务开发人员,能够在工作中用到的技术其实不多.虽然平时老是说什么,多线程,并发,注入,攻击!但是在实际工作中,这些东西不见得用得上.因为,我们用的框架已经把这些事做掉了. 比如web开发,外面有大 ...
随机推荐
- Red Hat Enterprise Linux Server 6.5安装GCC 4.9.2
现在很多程序员都应用GCC,怎样才能更好的应用GCC.目前,GCC可以用来编译C/C++.FORTRAN.JAVA.OBJC.ADA等语言的程序,可根据需要选择安装支持的语言.本文以在RedHat L ...
- Java中的集合排序
1. 定义排序 class ComparatorDefault implements Comparator { public int compare(Object arg0, Object arg1) ...
- extjs学习资料
ExtJs 入门教程 1.Extjs5.1.0教程云盘地址 http://pan.baidu.com/s/1qYhHiEw 2.Extjs3.x如下: ExtJs 入门教程一[学习方法] ExtJ ...
- 【转】What is an SDET
What is an SDET? SDET stands for Software Development Engineer in Test (or Software Design Engineer ...
- hdu 5652 India and China Origins 并查集
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5652 题目大意:n*m的矩阵上,0为平原,1为山.q个询问,第i个询问给定坐标xi,yi,表示i年后这 ...
- log4j.properties配置
一.日志:除了能记录异常信息,还可以记录程序正常运行时的关键信息. 使用log4j来进行日志文件记录经典步骤: 01.在项目中创建一个lib文件夹,然后将下载好的jar包copy到该文件夹下 02.对 ...
- IO(四)----对象的序列化
对象的序列化: 将内存中的对象直接写入到文件设备中. 对象的反序列化: 将文件设备中持久化的数据转换为内存对象. 自定义类只要实现了Serializable接口,便可以通过对象输入输出流对对象进行 ...
- EasyUI datagrid : 启用行号、固定列及多级表头后,头部行号位置单元格错位的问题
症状如图: 上图中,行号列与checkbox 列融合了.解决方法是在datagrid 的 onLoadSuccess 事件中加入如下代码: var opts = $(this).datagrid('o ...
- CentOS 新增swap交换空间
在centos 6.4 64位系统中安装oracle 10g数据库软件,但由于交换空间过小导致检查不通过: 因此需要增加交换空间的大小. 第一步:在opt目录下新建swap交换文件,并设置其大小为2G ...
- python基础-模块
一.模块介绍 ...