1. 多线程的三种实现方式
1.1 继承Thread类
实现步骤:
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程start
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//start() 开启线程; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}
1.2 实现Runnable接口
实现步骤:
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"坦克");
Thread t2 = new Thread(my,"飞机");
//启动线程
t1.start();
t2.start();
}
}
1.3 利用Callable和Future接口实现
实现步骤:
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为其构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为其构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟她表白第" + i + "次");
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//开启线程
t1.start();
//得到线程运行完毕后的结果
String s = ft.get();
System.out.println(s);
}
}
2. 线程类的常见方法
设置和获取线程名称:
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为参数name |
String getName( ) | 返回此线程的名称 |
Thread currentThread( ) | 返回对当前正在执行的线程对象的引用 |
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//void setName(String name):将此线程的名称更改为等于参数 name
my1.setName("高铁");
my2.setName("飞机");
my1.start();
my2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());//main
}
}
线程类的其他方法:
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
t2.setDaemon(true);
t1.start();
t2.start();
}
}
3. 线程的安全问题
当多个线程一起操作共享数据时会产生数据安全问题
解决方法:
- 同步代码块:synchronized(任意对象),相当于给代码加锁了,任意对象就可以看成是一把锁
- 同步方法:把synchronized关键字加到方法的返回值类型前,同步方法的锁对象是this、同步静态方法的锁对象:类名.class
- Lock锁:Lock中提供了获得锁和释放锁的方法
同步代码块:
public class Ticket implements Runnable {
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
//对可能有安全问题的代码加锁,多个线程必须使用同一把锁
synchronized (obj) {
if (ticket == 0) {
break;
} else {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(
Thread.currentThread().getName() +
"卖了一张票, 还剩" + ticket + "张");
}
}
}
}
}
同步方法:
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
boolean result = synchrMethed();
if (result){
break;
}
}
}
private synchronized boolean synchrMethed() {
if (ticket == 0){
return true;
}else {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+" 卖了一张票,还剩 " + ticket +"张票");
return false;
}
}
}
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化:
import java.util.concurrent.locks.ReentrantLock;
public class Ticket02 implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket == 0) {
break;
} else {
Thread.currentThread().sleep(100);
ticket--;
System.out.println(
Thread.currentThread().getName() + "卖了一张票, 还剩" + ticket + "张");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
4. 死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程都处于等待状态,无法执行的状态
在资源有限、同步嵌套的情况下会产生死锁现象
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
//线程一
synchronized (objB){
System.out.println("小康同学正在走路");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("小薇同学正在走路");
}
}
}
}).start();
}
}
5. 经典同步问题:生产者—消费者问题
问题描述:
生产者和消费者进程,共享一个初始为空、容量为1的缓冲区,只有缓冲区为空时,生产者才能将消息放入缓冲区,否则等待;只有缓冲区不空时,消费者才能从缓冲区取出消息,否则等待。缓冲区为临界资源,同一时间只允许消费者或者生产者访问。
Object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
缓冲区:
public class Buffer {
//定义一个标记
//true 就表示缓冲区有消息,允许消费者执行
//false 就表示缓冲区没有消息,允许生产者执行
private boolean flag;
//count表示生产者最多生产消息的数量
private int count;
//锁对象
private final Object lock = new Object();
public Buffer() {
this(false,10); //默认缓冲区没有消息,生产者生产10个消息就退出程序
}
public Buffer(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
@Override
public String toString() {
return "Desk{" +
"flag=" + flag +
", count=" + count +
", lock=" + lock +
'}';
}
}
生产者:
public class Producer extends Thread{
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true){
synchronized (buffer.getLock()){
if(buffer.getCount() == 0){
break;
}else{
if(!buffer.isFlag()){
//生产
System.out.println("生产者生产一个消息放入缓冲区");
buffer.setFlag(true);
buffer.getLock().notifyAll();
}else{
try {
buffer.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
消费者:
public class Consumer extends Thread{
private Buffer buffer;
public Consumer(Buffer desk) {
this.buffer = desk;
}
@Override
public void run() {
//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while(true){
synchronized (buffer.getLock()){
if(buffer.getCount() == 0){
break;
}else{
if(buffer.isFlag()){
//有
System.out.println("消费者从缓冲区取出一个消息");
buffer.setFlag(false);
buffer.getLock().notifyAll();
buffer.setCount(buffer.getCount() - 1);
}else{
//没有就等待
//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
try {
buffer.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
测试:
public class Demo {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Consumer consumer = new Consumer(buffer);
Producer producer = new Producer(buffer);
consumer.start();
producer.start();
}
}
使用阻塞队列实现等待唤醒机制
常见阻塞队列BlockingQueue:
- ArrayBlockingQueue: 底层是数组,有界
- LinkedBlockingQueue: 底层是链表,无界。但不是真正的无界,最大为int的最大值
BlockingQueue的核心方法:
- put(anObject): 将参数放入队列,如果放不进去会阻塞
- take(): 取出第一个数据,取不到会阻塞
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
//定义一个阻塞队列,容量为1
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Consumer1 consumer1 = new Consumer1(bd);
Producer1 producer1 = new Producer1(bd);
consumer1.start();
producer1.start();
}
}
//消费者
class Consumer1 extends Thread{
private ArrayBlockingQueue<String> bd;
public Consumer1(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while(true){
try {
bd.take();
//System.out.println("消费者取出一个消息");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer1 extends Thread{
private ArrayBlockingQueue<String> bd;
public Producer1(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true){
try {
bd.put("消息");
//System.out.println("生产者生产一个消息");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如有错误欢迎留言评论,及时更正。 2021年6月15日 羽露风