目录
线程的生命周期
线程的同步
Synchronized的使用方法
同步机制中的锁
同步的范围
单例设计模式之懒汉式(线程安全)
线程的死锁问题
Lock(锁)
synchronized 与 Lock 的对比
线程的通信
JDK5.0 新增线程创建方式
新增方式一:实现Callable接口
新增方式二:使用线程池
总结
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的
五种状态:
class Ticket implements Runnable {
private int tick = 100;
public void run() {
while (true) {
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + "售出车票,tick为:"+ tick--);
}else
break;
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
2. 问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
3. 解决办法:
对多条操作共享数据的语句,.只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行
synchronized (对象){// 需要被同步的代码;}
同步锁机制:
在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么?
注意:
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。 应尽量避免使用suspend()和resume()来控制线程
class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance == null){
instance=new Singleton();
}
}
}
return instance;
} }
public class SingletonTest{
public static void main(String[] args){
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
}
}
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
} finally{
lock.unlock();
}
}
}
注意:如果同步代码有异常,要将unlock()写入finally语句块
相同:二者都可以解决线程安全问题 不同 synchronized机制在执行完相应的同步代码以后,自动释放同步监视器 lock 需要手动的启动同步lock,同时结束也需要手动释放unlock
class Communication implements Runnable {
int i = 1;
public void run() {
while (true) {
synchronized (this) {
notify();
if (i <= 100) {
System.out.println(Thread.currentThread().getName() +":" + i++);
} else
break;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明
wait() 方法
notify()/notifyAll()
与使用Runnable相比, Callable功能更强大些
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
线程池相关API
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
package com.jyc.p1;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/*
栗子:创建三个窗口卖票,总票为100张,使用实现 Runnable 接口的方式
1.问题卖票过程中,出现了重票,错票---》线程安全问题
2.问题出现的原因:在某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完 ticket 时
其他线程才可以开始操作 ticket,这种情况线程a出现了阻塞,也不能被改变
4.在java中我们通过同步机制,来解决线程的安全问题
方式1.同步代码块
synchronized (同步监视器){
//需要被同步的代码
}
①.操作共享数据的代码,即为需要同步的代码
②.共享数据,多个线程共同操作的变量,比如 ticket
③.同步监视器,俗称锁,任何一个类的对象,都可以充当锁(多个线程必须共用一把锁)
④ 在实现 Runnable 接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
方式2.同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
同步方法仍是同步监视器,只是我们不需要显示的声明,
非静态同步方法,同步监视器是this
静态同步方法,同步监视器是当前类本身
同步的方式,解决了线程的安全问题
操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
synchronized 与 lock 的异同
相同:二者都可以解决线程安全问题
不同 synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
lock 需要手动的启动同步lock,同时结束也需要手动释放unlock
* */
class window implements Runnable{
private int ticket=100;
Object obj= new Object();
// 方式一 :同步代码块
// public void run() {
// //此时的this 唯一的window对象
// synchronized (this){//synchronized (obj){
// while (true){
// if (ticket>0){
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
// ticket--;
// }else{
// break;
// }
// }
// }
//
// }
// 方式三
// 1.实例化ReentrantLock
private ReentrantLock lock=new ReentrantLock();
public void run() {
// while (true){
// show();
// }
while (true){
try{
//调用锁定方法lock()
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}finally {
//调用解锁的方法unlock
lock.unlock();
}
}
}
//方式二 同步方法
private synchronized void show(){ //同步监视器 this
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}
}
}
/*
* 单例模式 懒汉式
*
* */
class Bank{
private Bank(){}
private static Bank
instance=null;
//方式一.同步监视器为 Bank.class 类也为对象
// public static synchronized Bank getInstance(){
// if (instance==null){
// instance=new Bank();
//
// }
// return instance;
public static Bank getInstance(){
if (instance==null){
synchronized (Bank.class){
if (instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
/*
* 线程通讯的例子
* 涉及到三个方法
* wait() 一但执行此方法线程就进入阻塞状态,并释放同步监视器
* notify() 一但执行次此方法,就会唤醒wait的一个线程,如果有多个线程被wait就唤醒优先级高的那个
* notifyAll 一但执行次此方法,就会唤醒wait的所有线程
* 说明 wait notify notifyAll 这三个方法必须使用在同步代码块或同步方法中
* 三个方法的调用者必须是同步代码块,或同步方法中的同步监视器
* 定义在object中
*
* sleep 和 wait 的异同
* 相同点 一但 执行方法,都可以使当前线程进入阻塞状态,
* 不同点 1.两个方法声明的位置不同,Thread类中声明的sleep,object类中声明的wait
* 2. 调用的要求不同,sleep可以在任何场景下调用,wait方法必须使用在同步代码块或者同步方法中
* 3.关于是否释放同步监视器,如果两个方法都使用在同步代码块,或同步方法中,sleep()不会释放锁
* wait()会释放锁
*
*
* */
class Number implements Runnable{
private int number=1;
public void run() {
while (true){
synchronized (this){
notify();
if (number<=100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" : "+number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
/*
*创建线程的方式三 实现 Callable 接口
* 如何理解实现 Callable接口的方式 比实现 Runnable 接口创建多线程的方式强大
* call()可以有返回值
* call()可以抛出异常
* Callable 支持泛型
*
*
* */
//创建一个实现了 Callable 的实现类
class NumThread implements Callable{
//实现call方法 将此线程需要执行的操作声明在call方法中
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 1; i <=100 ; i++) {
if(i%2==0){
sum+=i;
}
}
return sum;
}
}
/*
* */
public class Shkstart {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//演示线程安全问题------------
// window w=new window();
// Thread t1 =new Thread(w);
// Thread t2 =new Thread(w);
// Thread t3 =new Thread(w);
// t1.setName("窗口一");
// t2.setName("窗口二");
// t3.setName("窗口三");
// t1.start();
// t2.start();
// t3.start();
//线程通讯的演示----------
// Number number=new Number();
// Thread t1=new Thread( number);
// Thread t2=new Thread( number);
// t1.setName("线程一");
// t2.setName("线程二");
// t1.start();
// t2.start();
// *创建线程的方式三 实现 Callable 接口---------------
//3.创建实现Callable接口实现类的对象
NumThread numThread=new NumThread();
//4.将此Callable接口实现的对象 传递到 FutureTask 构造器中 创建 FutureTask 对象
FutureTask futureTask = new FutureTask(numThread);
//5.将 FutureTask的对象作为参数 传递到 Thread类的构造器中并调用start
new Thread(futureTask).start();
Object sum= futureTask.get();
//6.获取 Callable中 call方法的返回值
System.out.println(sum);
// * 创建线程的方式四 使用线程池
// 提高响应速度(减少了创建新线程的时间)
// 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
// 便于线程管理
// corePoolSize:核心池的大小
// maximumPoolSize:最大线程数
// keepAliveTime:线程没有任务时最多保持多长时间后会终止
//1.提供指定线程数量的线程池
ExecutorService service=Executors.newFixedThreadPool(10);
// 设置线程池的属性
//2.执行指定线程的操作实现 Callable或 Runnable 的对象
// service.submit()
// service.execute();
//3.关闭链接池
service.shutdown();
}
}