java多线程编程
参考书籍:Java多线程编程核心技术(高洪岩)
一、java多线程技能
1.线程的启动
-
- 进程:QQ.exe
- 线程:QQ发一条消息由一个线程处理,QQ传输文件由一个线程处理
- 创建线程:使用多线程的时候,代码运行结果与代码执行顺序和调用顺序是无关的。使用多线程的时候可能上一行的代码还没执行完,就执行了下一行代码。
- 继承Thread类
1 class A extends Thread { 2 public void run(){ 3 //... 4 } 5 } 6 public class Demo { 7 public static void main(String[] args) { 8 A a = new A(); 9 a.start(); 10 } 11 }
-
-
- 实现Runnable接口
-
1 class A implements Runnable{ 2 public void run() { 3 //... 4 } 5 } 6 public class Demo { 7 public static void main(String[] args) { 8 A a = new A(); 9 Thread t = new Thread(a); 10 t.start(); 11 } 12 }
-
- 多线程访问同一资源避免非线程安全问题(使用synchronized修饰)
public class Mythread extends Thread { private int count = 5; @Override synchronized public void run() { super.run(); count--; System.out.println("由"+this.currentThread().getName()+"计算,count="+count); } } public class Test { public static void main(String[] args) { try { Mythread thread = new Mythread(); Thread a = new Thread(thread,"A"); Thread b = new Thread(thread,"B"); Thread c = new Thread(thread,"C"); Thread d = new Thread(thread,"D"); Thread e = new Thread(thread,"E"); a.start(); b.start(); c.start(); d.start(); e.start(); } catch (Exception e) { e.printStackTrace(); } } }
2.常用方法
public class Test { public static void main(String[] args) { try { Thread.currentThread();//返回代码段正在被哪个线程调用的信息 Thread.currentThread().getName();//获取当前线程的名称 Thread.currentThread().isAlive();//当前线程是否处于活跃状态 Thread.sleep(1000);//让线程休眠1秒 Thread.currentThread().getId();//获取当前线程的唯一标识 Thread.currentThread().interrupt();//停止当前线程(此方法只是打一个停止线程的标记,并非真正停止线程,线程会继续执行) Thread.currentThread().interrupted();//测试当前线程是否已经中断,同时清除该中断状态 Thread.currentThread().isInterrupted();//测试当前线程是否已经中断,只是测试,不做其他处理 Thread.currentThread().stop();//弃用的方法,直接停止线程,使一些请理性工作无法完成,同时解锁锁定的对象,不能保证数据一致 Thread.currentThread().suspend();//弃用的方法,暂停线程,使用此方法可能会造成对公共对象的独占,使其他线程无法访问公共同步对象 Thread.currentThread().resume();//弃用的方法,继续暂停的线程 Thread.currentThread().setPriority(10);//设置优先级,1 <= x <= 10; Thread.currentThread().yield();//放弃当前cpu资源,让给其他任务去独占cpu } catch (Exception e) { e.printStackTrace(); } } }
3.守护线程
守护线程是一种特殊的线程,当进程中不存在非守护线程时,守护线程则自动销毁。典型的守护线程是垃圾回收线程。将线程的daemon设置为true,则该线程作为守护线程存在。如下代码,将main中new出的thread线程设置daemon为true,作为main线程的守护线程,下面代码输出结果为

每一秒输出一个数字,可能不会输出i=6,也可能i=6在end之后输出。当main线程sleep结束,执行“System.out.println("end");”之后,main线程停止,thread作为守护线程同时停止。
public class Mythread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true){
i++;
System.out.println("i= " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
try {
Mythread thread = new Mythread();
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("end");
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、对象及变量的并发访问
1.synchronized同步方法
-
- 方法内定义的变量线程安全,实例变量(C中的全局变量)非线程安全
- synchronized关键字可以用在方法上加在方法之前。对方法内的对象加锁,从而实现同步。
- 多个对象多个锁:多个线程分别访问不同的对象时,会创建多个锁,线程安全(没有共享变量因此不存在非线程安全问题),但不是同步执行。
- 脏读:读取变量时,此值被其他线程修改过。synchronized可以解决脏读的问题。
- synchronized锁重入:当一个线程获得一个对象锁后,再次请求此对象锁是可以再次得到该对象锁的。
- 出现异常的时候锁会自动释放。
- 同步不具有继承性:父类方法用synchronized修饰,但是子类方法没有用synchronized修饰,则子类非线程安全。
2.synchronized同步代码块
-
- synchronized方法的弊端:同步方法运行速度慢,效率低下,可以用同步代码块实现的尽量用同步代码块实现。
- 同步代码块的使用:synchronized(this){},同步代码块是锁定当前对象的。
- 同步代码块实现的效果是一半同步一半异步的。
- 将任意对象作为对象监视器:synchronized(Object obj){}锁定obj对象。
- synchronized的三个结论
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
- 当其他线程执行x对象中synchronized同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized(this){}代码块时呈同步效果。
- 静态同步synchronized方法和synchronized(class)代码块:锁class类。验证不是一个锁的方式:一个类中两个static用synchronized修饰,一个非static的用synchronized修饰方法,分别启动线程调用这三个方法。
- 数据类型String的常量池特性(String常量池可能导致某个线程无法启动,因此在同步代码块中一般不使用String作为锁对象,而是使用其他)
- 同步synchronized方法无限等待与解决:使用同步代码块解决。
- 多线程死锁:监测是否有死锁发生:1>cmd 2>进入jdk安装文件bin目录(cd C:/Users/administrator/AppData/...../com.sun.java.jdk.win32.x86_1.6.0.013/bin) 3>执行jsp命令找到运行的线程run标志的id 4>执行jstack命令(jstck -l 3244)
- 内置类与静态内置类
- 锁对象的改变:只要对象不变,即使对象的属性变了,运行结果还是同步的。注意String类型,String使用的是引用,修饰值的时候可能会改变对象。
3.volatile关键字
-
- 在启动线程后,变量的值是存在与公共内存和线程的私有内存中的,volatile是保证当前线程强制性的从公共内存中取值。所以volatile一般用于修饰只读的变量,对于修改的变量并不能保证线程的安全。
- synchronized和volatile的比较
- volatile是线程同步的轻量级实现,性能比synchronized好;volatile只能修饰变量,synchronized能修饰方法,代码块
- 多线程访问volatile不会发生阻塞,synchronized会出现阻塞
- volatile只能保证数据的可见性,不能保证原子性,synchronized会将私有内存和公共内存做数据同步,可以保证原子性和可见性。
- 原子类 java.util.concurrent.atomic.*;该包下的对象操作是原子操作(从内存中取值,修改值,放入到内存。原子操作,线程安全)。但是当在一个非synchronized修饰的方法中对原子类对象进行多个操作时,也是非线程安全的。(每个操作是线程安全,多个操作之间非线程安全)
三、线程间的通信
1.等待/通知机制
-
- wait()和notify();wait和notify方法只能在同步方法或者同步方法块中执行;wait方法作用是使当前线程进行等待,释放对象锁,wait之后的代码不再执行;notify由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出notify通知,并使它等待获取该对象的对象锁。执行notify之后,待将nofity下面的代码执行完成(退出synchronized)之后,释放对象锁。【wait使线程停止运行,notify使停止的线程继续运行】
- 当interrupt()方法遇到wait()方法:对wait状态的线程执行interrupt()方法,抛出InterruptedException
- notify和notifyAll:notify唤醒一个线程,notifyAll唤醒所有的线程
- wait(long):在long的时间内如果有其他线程唤醒该线程,该线程被唤醒,否则在long时间后,自动唤醒。
- 生产者/消费者模式实现:一个线程赋值,另一个线程取值。1>多个生产者和多个消费者造成的假死问题2>notify解决假死问题3>操作栈的实现
- 通过管道进行线程间通信:字节流PipedInputStream和PipedOutputStream字符流:PipedReader和PepedWriter;核心:connect的使用(inputStream.connect(outputStream)和outputStream.connect(inputStream))
2.方法join的使用
-
- join和join(long):join主线程调用join方法,在子线程执行完成后,主线程继续执行。join(long)主线程在long时间内等待子线程执行,期间内,子线程返回主线程立马结束等待状态,long时间后子线程没有返回,主线程不再等待,继续执行。
- join和interrupt方法彼此遇到,会出现异常
- join(long)和sleep(long)的区别:join(long)内部由wait(long)实现。所以join(long)在进入等待状态之后会释放对象锁,而sleep(long)不会释放对象锁
3.类ThreadLocal的使用
-
- 使每个线程有自己的公有变量,用于存储线程的私有数据。set()和get()
4.InheritableThreadLoca的使用
-
- InheritableThreadLocal:在子线程中获得父线程中继承下来的值。
- 值继承再修改

浙公网安备 33010602011771号