【线程 同步】
第一章 Thread
1.1 Thread类
/**
* @author 270
* 获取线程的名称:
* 1.使用Thread中的方法getName()
* String getName() 返回该线程的名称
* 2.可以获取到当前正在执行的线程,使用线程中的方法getName()获取名称
* static Thread currentThread() 返回对当前正在执行的线程对象的引用
* 输出格式:Thread[Thread-1,5,main]
*
* (中括号中的三个数据分别为:正在执行线程,线程优先级,线程所属线程组)
*
*/
/**1.定义Thread的子类
* @author 270*/
public class MyThread extends Thread{
/**2.重写run方法,设置线程任务*/
@Override
public void run() {
/**用getName重写*/
String name = getName();
System.out.println(name);
/**用currentThread重写*/
/* Thread t = Thread.currentThread();
System.out.println(t);*/
/**链式编程*/
System.out.println(Thread.currentThread().getName());
}
}
package Demo01;
/**
* @author 270
* 线程名称:
* 主线程:main
* 新线程:Thread-0
*/
public class Demo01GetThreadName {
public static void main(String[] args) {
/**创建Thread类子类对象*/
MyThread myThread = new MyThread();
/**开启新线程*/
myThread.setName("厄斐琉斯");
myThread.start();
/**开启新线程*/
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
/**链式编程获取主线程名称*/
System.out.println(Thread.currentThread().getName());
}
}
1.2 第二种创建线程的方法
/**
* @author 270
* 创建线程的第二种方式:实现Runnable接口
* java.lang.Runnable
* Runnable接口应该由那些打算通过某一线程执行其实例的类来实现
* 类必须定义一个称为run的无参数方法
* java.lang.Thread类的构造方法
* Thread(Runnable target)分配新的Thread对象
* Thread(Runnable target,String name)分配新的Thread对象。
* 实现步骤:
* 1.创建一个Runnable接口的实现类
* 2.在实现类中重写Runnable接口的run方法,设置线程任务
* 3.创建Runnable接口的实现类对象
* 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
* 5.调用Thread类中的start方法,开启新的线程执行run方法
*
*/
public class Demo01Runnable {
public static void main(String[] args) {
/**3.创建Runnable接口的实现类对象*/
RunnableImpl runnable = new RunnableImpl();
/**4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象*/
Thread thread = new Thread(runnable);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
实现类
package Demo02;
/**
* @author 270
*/
/**1.创建一个Runnable接口的实现类*/
public class RunnableImpl implements Runnable{
@Override
/** 2.在实现类中重写Runnable接口的run方法,设置线程任务*/
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
实现Runnable接口创建多线程程序的好处
* 1.避免了单继承的局限性
* 一个类只能继承一个类(一个人只能有亲爹),类继承了Thread类就不能继承其他的类
* 实现了Runnable接口,还可以继承其他的类,实现其他接口
* 2.增强了程序的扩展性,把设置线程任务和开启新线程进行了分离(解耦)
* 实现类中,重写了run方法,用来设置线程任务
* 创建Thread类对象,调用start方法,用来开启新线程
1.3匿名对象类实现线程创建
package Demo02;
/**
* @author 270
* 匿名内部类实现线程的创建
*
* 格式:
* new 父类/接口(){
* 重复父类/接口中的方法
* };
* 实现Runnable接口创建多线程程序的好处
* 1.避免了单继承的局限性
* 一个类只能继承一个类(一个人只能有亲爹),类继承了Thread类就不能继承其他的类
* 实现了Runnable接口,还可以继承其他的类,实现其他接口
* 2.增强了程序的扩展性,把设置线程任务和开启新线程进行了分离(解耦)
* 实现类中,重写了run方法,用来设置线程任务
* 创建Thread类对象,调用start方法,用来开启新线程
*/
public class Demo02InnerClass {
public static void main(String[] args) {
/**继承的匿名方法*/
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}.start();
/**接口的匿名实现类简化版*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"270");
}
}).start();
}
}
第二章 线程安全问题
2.1线程安全
2.2同步
三种方式实现同步操作:
1.同步代码块
2.同步方法
3.锁机制
2.3 同步代码块
同步代码块解决多线程安全问题:package Demo03; /** * @author 270 * 卖票问题出现的线程安全问题 * 卖出不存在的票和重复的票 * * 解决线程安全问题的一种方案:使用同步代码块 * 格式: * synchronized(锁对象){ * 可能会出现线程安全问题的代码(访问了共享数据的代码) * } * 【注意】: * 1.通过代码块中的锁对象,可以使用任意的对象 * 2.但是必须保证多个线程使用的锁对象是同一个 * 3.锁对象作用: * 把同步代码块锁住,只让一个线程在同步代码块中执行 */ public class RunnableImpl implements Runnable { /**定义多个线程共享的票源*/ private int ticket = 1000; /**创建锁对象*/ Object object = new Object(); /**设置线程任务:卖票*/ @Override public void run() { while (true){ /**同步代码块*/ synchronized (object){ /**判断票是否存在*/ if(ticket>0){ try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } /**存在票,卖票*/ System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票"); ticket--; } } } } }
2.4 同步方法
package Demo03;
/**
* @author 270
* 卖票问题出现的线程安全问题
* 卖出不存在的票和重复的票
* <p>
* 解决线程安全问题的第二种种方案:使用同步方法
* 使用步骤:
* 1.把访问了共享数据的代码抽取出来,放到一个方法中
* 2.在方法上添加synchronized修饰符
*
* 同步方法也会把方法内部的代码锁住
* 只让一个线程执行
* 同步方法的对象是谁
* 就是实现类对象 new RunnableImpl()
* 也就是this
*/
public class RunnableImpl implements Runnable {
/**
* 定义多个线程共享的票源
*/
private int ticket = 1000;
/**
* 创建锁对象
*/
Object object = new Object();
/**
* 设置线程任务:卖票
*/
@Override
public void run() {
while (true) {
payTicket();
}
}
/**
* 静态同步方法(了解)
* 将 public synchronized void payTicket()和ticket用static关键字修饰
* private static int ticket = 1000;和public static synchronized void payTicket() {
*
* 同步静态方法的锁对象不是this
* this是创建对象之后产生的,静态方法优先于对象
* 静态方法的锁对象是本类的class属性——>class文件对象(反射)
* 这里写Runnable.class
* */
/**
* 定义一个同步方法
*/
public synchronized void payTicket() {
/**判断票是否存在*/
//synchronized(this){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**存在票,卖票*/
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
//}
}
2.5 Lock锁
package Demo04.Demo03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 270
* 卖票问题出现的线程安全问题
* 卖出不存在的票和重复的票
* <p>
* 解决线程安全问题的第三种种方案:使用Lock锁
* java.util.concurrent.locks.lock接口
* Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
* Lock接口中的方法:
* void lock()
* 获得锁。
* void unlock()
* 释放锁。
* java.util.concurrent.locks.ReentrantLock implements Lock接口
* 使用步骤:
* 1.在成员位置创建一个ReentrantLock对象
* 2.在可能会出现安全问题的代码之前调用lock接口中的方法lock获取锁
* 3.在可能会出现安全问题的代码之后调用lock接口中的方法unlock释放锁
*/
public class RunnableImpl implements Runnable {
/**
* 定义多个线程共享的票源
*/
private int ticket = 1000;
/**1.在成员位置创建一个ReentrantLock对象*/
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
//判断票是否存在
if(ticket>0){
l.lock();
try{Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}catch (InterruptedException e){
e.printStackTrace();
}finally {
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}
try…catch…finally
try之前紧跟lock()方法
finally中第一行写unlock()方法
第三章 线程状态(详见pdf)
等待唤醒案例
package Demo05;
/**
* @author 270
* 等待唤醒案例:线程间的通信
* 创建顾客线程(消费者):告知商品的种类和数量,调用wait方法,放弃cpu执行进入waitting状态
* 创建一个老板线程(生产者):花5秒准备商品,准备好之后调用notify方法,唤醒顾客领取
*
*【注意】:
* 老板和顾客必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
* 同步使用的锁对象必须保证唯一
* 只有锁对象才能用wait和notify方法
*
* void wait()
* 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
*
* void notify()
* 唤醒正在等待对象监视器的单个线程。
* 会继续执行wait方法之后的代码
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建锁对象
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
//保证等待和唤醒线程只能有一个执行,所以需要使用同步技术
synchronized (obj){
System.out.println("告知商品种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("商品已经准备好了,可以提取了");
}
}
}.start();
/**创建老板的线程*/
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);//花五秒做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒的线程只有一个在执行,使用同步技术
synchronized (obj) {
System.out.println("老板5秒后准备好商品,准备好后告知顾客");
obj.notify();
}
}
}.start();
}
}
package Demo05;
/**
* @author 270
* 进入到TimeWaiting(计时等待)
* 方式一:使用sleep(long m)方法,毫秒值结束后线程睡醒进入Runnable状态
* 方式二:使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒就会自动醒来
*
* 唤醒的方法:
* void notify()
* 唤醒正在等待对象监视器的单个线程。
* void notifyAll()
* 唤醒正在等待对象监视器的所有线程。
*/
public class Demo02WaitAndNotify {
public static void main(String[] args) {
//创建锁对象
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while (true){
//保证等待和唤醒线程只能有一个执行,所以需要使用同步技术
synchronized (obj){
System.out.println("顾客一告知商品种类和数量");
try {
obj.wait(5000);//这里加入时间参数后就算老板没做好商品,也会执行唤醒代码
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("商品已经准备好了,顾客一可以提取了");
System.out.println("================");
}
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true){
//保证等待和唤醒线程只能有一个执行,所以需要使用同步技术
synchronized (obj){
System.out.println("顾客二告知商品种类和数量");
try {
obj.wait(5000);//这里加入时间参数后就算老板没做好商品,也会执行唤醒代码
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("商品已经准备好了,顾客二可以提取了");
System.out.println("================");
}
}
}
}.start();
/**创建老板的线程*/
new Thread(){
@Override
public void run() {
while (true){
try {
Thread.sleep(5000);//花五秒做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒的线程只有一个在执行,使用同步技术
synchronized (obj) {
System.out.println("老板5秒后准备好商品,准备好后告知顾客");
/* obj.notify();*/
/**唤醒所有等待的线程*/
obj.notifyAll();
}
}
}
}.start();
}
}