Java多线程编程
线程的创建
三种创建线程的方式:
如图所示:创建线程需要从上面几个方法实现线程。最重要的是Runnable接口
Threa类创建线程
线程:是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
创建新执行线程有两种方法。一种方法是将类声明为 Thread
的子类。该子类应重写 Thread
类的 run
方法。接下来可以分配并启动该子类的实例。
package com.wyx;
public class TestThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("调用线程"+i);
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
/*
调用start方法线程同时运行,
调用run方法,走完调用线程后接着走主线程
*/
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("这里是主线程"+i);
}
}
}
实现Runnable接口
声明实现 Runnable
接口的类。该类然后实现 run
方法。然后可以分配该类的实例,在创建 Thread
时作为一个参数来传递并启动。
package com.wyx;
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("调用线程"+i);
}
}
public static void main(String[] args) {
// 创建线程实现类
MyThread myThread = new MyThread();
// 将实现类对象放入Thread类中
Thread thread = new Thread(myThread);
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("这里是主线程"+i);
}
}
}
总结:
Thread类和Runable接口都能实现多线程,因为Java实行单继承方式,所以推荐使用Runable接口。
线程不安全性
package com.wyx;
public class TestRunnable implements Runnable{
private int ticketNums = 20;
@Override
public void run() {
while (true){
if(ticketNums==0){
break;
}
System.out.println(Thread.currentThread().getName()+":拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
TestRunnable ticket = new TestRunnable();
new Thread(ticket,"爸爸").start();
new Thread(ticket,"妈妈").start();
new Thread(ticket,"儿子").start();
}
}
执行以上代码出现了线程的不安全,两个人同时获得了第20张票(利用后面的线程同步解决)
实现Callable接口
- 实现Callable接口,需要返回值类型。
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorServices ser = Executors.newFixedThreadPool(1)
- 提交执行:Future
result = ser.submit(t1) - 获取结果:boolean re = result.get()
- 关闭服务:ser.shutdownNow();
静态代理模式(线程的底部实现原理)
- 真实对象和代理对象都需要实现同一个接口
- 代理对象要代理真实角色
优点:代理对象可以做真实对象做不了的事情。真实对象专注做自己的事情。
Lambda 表达式
函数式编程:
优点:
- 避免匿名内部类过多
- 可以让代码更简洁
- 去掉没有意义的代码,只留下核心的逻辑
函数式接口:任何一个接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。如下就是一个函数式接口
public interface Runnable{
public abstract void run();
}
对于一个函数式接口,我们可以通过 Lambda 表达式创建该接口的对象
package com.wyx;
//推导Lambda表达式
public class TestLambda {
public static void main(String[] args) {
// 3.获取实例化对象
Ilike like = null;
// 4.编写lambda表达式重写方法
like = ()->{
System.out.println("哈哈哈");
};
// 5.调用方法
like.lambda();
/* 当输出方法只有一条语句时,可以省略大括号。
当传入参数之一一个时可以省略(),多个参数时参数用,隔开可以省略参数类型
*/
}
}
// 1.定义接口
interface Ilike{
void lambda();
}
// 2.定义接口实现类
class Like implements Ilike{
@Override
public void lambda() {
}
}
线程的状态
停止线程
不推荐使用 JDK 提供的stop()、destroy()方法。
推荐线程自己停下来。
建立使用一个标志位进行终止变量,当flag=false,则终止运行。
线程休眠
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常,使用时需要处理异常;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时,倒计时等。
每一个对象都有一个锁,sleep不会释放锁
线程礼让(yield)
- 让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
-
就是让CPU重新调度,礼让不一定成功!看CPU心情
线程强制执行(Join)
- join合并线程,等待线程执行完成后,在执行其他线程,其他线程阻塞如同插队一样
线程插队:意思是在完成之前必须完成VIP线程,但是从启动线程start方法开始,线程都在运行。
package com.wyx;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("VIP线程"+i);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new TestJoin());
thread.start();
for (int i = 0; i < 100; i++) {
if(i == 99){
try {
// 线程插队:意思是在完成之前必须完成VIP线程,但是重启动线程到现在都在运行线程。
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main线程"+i);
}
}
}
查看线程状态(State)
NEW 未启动状态
RUNNABLE 正在运行
TIMED_WAITING 等待状态
TERMINATED 结束状态结束后的进程不能重新启动
package com.wyx;
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()-> {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待两秒结束");
});
// 查看未启动的线程状态
Thread.State state = thread.getState();
System.out.println(state);
// 查看启动后线程转台
thread.start();
state = thread.getState();
System.out.println(state);
// 持续查看线程状态
while (state != Thread.State.TERMINATED){ //只要线程不停止就一种查看
try {
Thread.sleep(500); //这里代表打印的速度
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();
System.out.println(state);
}
}
}
线程的优先级
-
线程优先级用数字表示,范围0~10
-
优先级高不一定最先跑,但是大多数都是。
-
使用以下方式改变或获取优先级
getPriority():获取优先级
setPriority(int xx):更改优先级
package com.wyx;
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5= new Thread(myPriority);
// 先设置优先级在启动取值(0~10)
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(3);
t3.start();
t4.setPriority(8);
t4.start();
t5.setPriority(10);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程(setDaemon)
即守护没有执行完的线程,所有的小线程执行完后,守护线程也会停止
package com.wyx;
public class TestDaemon {
public static void main(String[] args) {
Thread god = new Thread(new god());
god.setDaemon(true); //默认是false表示是用户线程,正常线程都是用户线程。。。
god.start(); //启动守护线程
new Thread(new you()).start(); //启动用户线程
}
}
class god implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程");
}
}
}
class you implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程未结束");
}
System.out.println("用户线程结束");
}
}
线程同步(解决线程不安全)
由于我们可以通过private关键字来保证数据对象只能被访问,所以Java提供了针对同步方法的关键字:synchronized。它的包括两种用法:synchronized 方法和 synchronized 块
public synchronized void method(){}
synchronized 方法控制对 “对象” 的访问,每个对象对应一把锁,调用对象的锁才能执行此方法,方法一旦执行,就独占该锁,只有运行完成才能返回锁,后面的线程得到了锁才能继续执行。
缺点:将方法声明为 synchronized 将会影响效率,但是线程是安全的。
//synchronized 锁方法时默认锁这个方法的对象。即为 synchronized(this){},使用方法如下:
public synchronized void method(){}
//synchronized 块 即锁定代码块中需要修改数据,如需要修改票数,即传入包含票的类
synchronized (包含票数的实例化的对象){
修改的代码块;
}
Lock(锁)
Lock是显示锁(手动开启和关闭),synchronized 是隐式锁,
Lock只能锁代码块。
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。
ReentrantLock 类实现了Lock锁 使用方法如下:
package com.wyx;
import java.util.concurrent.locks.ReentrantLock;
public class TestRunnable implements Runnable{
private int ticketNums = 20;
//定义锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
// 加锁
lock.lock();
if(ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":拿到了第" + ticketNums-- + "张票");
}else {
break;
}
}
lock.unlock(); //解锁
}
public static void main(String[] args) {
TestRunnable ticket = new TestRunnable();
new Thread(ticket,"爸爸").start();
new Thread(ticket,"妈妈").start();
new Thread(ticket,"儿子").start();
}
}
// 注意因为线程加锁,所以只能是爸爸拿到票。只有爸爸的线程结束才能运行接下来的线程
线程协作
Java 提供了几个方法解决线程之间通信问题
管程法线程通信
简单来说,生产者生产东西,消费者消费东西,存放东西有一个缓冲区,缓冲区满了生产者停止生产,缓冲区空了消费者停止消费。
生产者消费者问题。。
package com.wyx;
//测试生产者消费者模型
public class Test {
public static void main(String[] args) {
Container container = new Container();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
Container c;
public Productor(Container c) {
this.c = c;
}
//生产
@Override
public void run() {
for (int i = 1; i < 100; i++) {
c.push(new Food(i));
System.out.println("生产了"+i+"份食物");
}
}
}
//消费者
class Consumer extends Thread{
Container c;
public Consumer(Container c) {
this.c = c;
}
// 消费
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println("消费了第"+c.pop().id+"份食物");
try {
Thread.sleep(100); //线程睡眠防止打印速度跟不上,显示先消费后生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//食物
class Food {
int id;
public Food(int id) {
this.id = id;
}
}
//食物暂存区
class Container {
// 容器大小
Food[] foods = new Food[5];
int count = 0; //容器计数器
// 生产者放入食物
public synchronized void push(Food food){
//如果容器满了,就通知生产者停止生产
if(count == (foods.length-1)){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,生产者放入产品
foods[count] = food;
count++;
//可以通知消费者消费
this.notifyAll();
}
// 消费者消费食物
public synchronized Food pop(){
// 判断能否消费
if (count == 0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费,则消费
count--;
Food food = foods[count];
// 通知生产者生产
this.notifyAll();
return food;
}
}
信号灯法
生产者和消费者相互制约,类似于交通路口,一次只能有一条路的车通过,因此也叫信号灯法。
public class light {
public static void main(String[]args)
{
Tv tv=new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv)
{
this.tv=tv;
}
public void run()
{
for(int i=0;i<20;i++)
{
if(i%2==0)
{
this.tv.play("偶不变");
}else
{
this.tv.play("奇变");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv)
{
this.tv=tv;
}
public void run()
{
for(int i=0;i<20;i++)
{
this.tv.watch("无聊");
}
}
}
//同一个资源 电视
class Tv {
String voice;
//信号灯
//为真则演员表演,观众等待
//为假则观众观看,演员等待
boolean flag=true;
//表演
public synchronized void play(String voice)
{
if(!flag)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("表演了:"+voice);
this.voice=voice;
//表演后
this.notifyAll();
this.flag=!this.flag;
}
public void watch(String string) {
// TODO Auto-generated method stub
}
//观看
public synchronized void watch()
{
if(flag)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("听到了:"+voice);
//观看后:
this.notifyAll();
this.flag=!this.flag;
}
}