线程、同步
Thread类
- getName()获取线程的名称
- currentThread()方法返回当前线程
- setName(String name)设置线程名称
创建多线程程序的方法一:继承类Thread
/**
* @author: 小码农
* @create: 2021-08-03 20:22
**/
public class Demo6 {
public static void main(String[] args) {
Thread_test t1 = new Thread_test();
t1.setName("小狗熊");
t1.start();
Thread_test t2 = new Thread_test();
t2.start();
}
}
/**
* @author: 小码农
* @create: 2021-08-03 20:47
**/
public class Thread_test extends Thread {
//重写run方法
@Override
public void run() {
System.out.println(Thread.currentThread());
System.out.println(this.getName());
}
}
Runnable接口
创建多线程程序的方法二:实现Runnable接口
步骤:
- 创建一个Runable接口的实现类
- 在实现类中重写Runable接口的run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中的start方法,开启新的线程执行run方法
/**
* @author: 小码农
* @create: 2021-08-05 13:33
**/
public class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
/**
* @author: 小码农
* @create: 2021-08-05 13:34
**/
public class Demo1 {
public static void main(String[] args) {
RunnableImpl ri = new RunnableImpl();
Thread t = new Thread(ri);
t.start();
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
继承Thread类和实现Runnable接口创建多线程的区别:
- 一个类只能继承一个父类,如果继承Thread类,就无法继承其它父类
- 如果实现Runnable接口,则还可以继续继承其它类
如果使用多线程,一般使用实现Runnable接口的方法
匿名内部类
匿名内部类:写在其它类的内部,没有名字的类
使用格式:
new 父类/接口(){
重写父类/接口的方法;
}
/**
* @author: 小码农
* @create: 2021-08-05 13:42
**/
public class Demo2 {
public static void main(String[] args) {
//使用Thread创建匿名内部类
new Thread(){
@Override
public void run(){
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}.start();
//使用Runnable接口的匿名实现类
new Thread(new Runnable(){
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}).start();
}
}
线程安全
多线程程序访问共同的数据源,有可能会出现线程安全问题。
线程同步
- 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
- 要解决多线程并发访问一个资源的安全性问题,java提供了同步机制来解决
- 有三种方式完成同步操作:
- 同步代码块
- 同步方法
- 锁机制
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示对这个区块的资源实行互斥访问。同一时间只有一个线程可以获取锁对象执行run方法。当其中一个线程获取锁对象执行run方法时,其它线程执行run方法没有锁对象会进入阻塞状态,直到上一个线程执行完run方法归还锁对象,下一个线程才获取锁对象并执行run
synchronized(同步锁对象){
需要同步的代码;
}
import java.util.concurrent.SynchronousQueue;
/**
* @author: 小码农
* @create: 2021-08-05 13:53
* 使用同步代码块解决线程安全问题
**/
public class Runnable3 implements Runnable {
private int ticket = 20;
//创建一个锁对象
Object o = new Object();
//买票
@Override
public void run() {
while (true) {
synchronized (o){
if (ticket>0){
System.out.println("票号:"+ticket);
ticket--;
}
}
}
}
}
同步方法:通过synchronized修饰符修饰方法,同步方法是将实现类的对象锁住,谁调用run方法就将该对象锁住
synchronized 返回值类型 方法名(参数){
方法内容;
}
/**
* @author: 小码农
* @create: 2021-08-05 13:53
* 使用同步方法解决线程安全问题
**/
public class Runnable4 implements Runnable {
private int ticket = 20;
//买票
@Override
public void run() {
sell();
}
//定义一个同步方法
public synchronized void sell(){
while (true) {
if (ticket>0){
System.out.println("票号:"+ticket);
ticket--;
}
}
}
}
/**
* @author: 小码农
* @create: 2021-08-05 15:10
**/
public class Demo4 {
public static void main(String[] args) {
Runnable4 r = new Runnable4();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
Lock锁
Lock锁机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有。
Lock锁也成为了同步锁,加锁与释放锁的方法:
- public void lock():加同步锁
- public void unlock():释放同步锁
使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能会出现安全问题的代码前调用lock接口中的lock方法获取锁
- 在可能会出现安全问题的代码前后调用lock接口中的unlock方法释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: 小码农
* @create: 2021-08-05 13:53
* Lock锁
* lock()获取锁
* unlock()释放锁
**/
public class Runnable5Lock implements Runnable {
private int ticket = 20;
Lock l = new ReentrantLock();
//买票
@Override
public void run() {
sell();
}
//定义一个同步方法
public synchronized void sell(){
while (true) {
//加lock锁
l.lock();
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
ticket--;
}
//释放lock锁
l.unlock();
}
}
}
/**
* @author: 小码农
* @create: 2021-08-05 15:10
**/
public class Demo5 {
public static void main(String[] args) {
Runnable5Lock r = new Runnable5Lock();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
线程状态
- New:新建状态,至今尚未启动的线程处于这种状态
- Runnable:运行状态,正在Java虚拟机中执行的线程处于这种状态
- Blocked:阻塞状态,当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程获得锁时,将变成Runnable状态
- Waiting:无限等待状态,一个线程在等待另一个线程执行一个唤醒动作时,该线程进入Waiting状态,进入这个状态后无法自动唤醒,必须等待另一个线程调用notify或者notifyAll方法才能唤醒
- TimedWaiting:和waiting一样,有几个方法有超时参数,调用他们将进入Timed Waiting状态,该状态将一直保持到超时期满或者收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
- Terminated:被终止,因为run方法正常退出而死亡,或因为没有捕获的异常终止了run方法而死亡
练习:线程的睡眠sleep()和唤醒notify()
/**
* @author: 小码农
* @create: 2021-08-05 15:44
* 线程状态练习:等待--唤醒
**/
public class Demo6 {
public static void main(String[] args) {
//创建对象
Object o = new Object();
//使用匿名内部类创建顾客线程
new Thread(){
@Override
public void run() {
while (true){
synchronized (o){
System.out.println("顾客:一个肉包");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒后执行的代码
System.out.println("顾客:开吃");
System.out.println("-- -- -- -- -- -- -- --");
}
}
}.start();
//创建老板线程
new Thread(){
@Override
public void run() {
while (true) {
//做包子5s
try {
System.out.println("老板:做包子需要5秒钟");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
System.out.println("老板:包子做好了");
o.notify();
}
}
}
}.start();
}
}
线程进入TimeWaiting(计时等待)的两种方式
- sleep(long m):在毫秒值结束后,自动唤醒进入Runnable或Blocked状态
- wait(long m):如果在毫秒值结束后,还没有被notify唤醒,就会自动醒来,进入Runnable或Blocked状态
唤醒线程的方法:
- notify():唤醒一个线程
- notifyAll():唤醒所有等待的线程