JAVA多线程
JAVA多线程
实现多线程
进程
- 指正在运行的程序,是系统进行资源分配和调用的独立单位
- 每个进程都有它自己的内存空间和系统资源
线程
- 是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序(比如记事本点开设置的时候无法操作输入文本)
- 多线程:一个进程如果有多条执行路径,则称为多线程程序(扫雷开始,时间自己会开始计时)
多线程的实现方式---继承Thread类
- 方式1:继承Thread类
- 定义一个类继承Thread类
- 再类中重写run()方法
- 创建类的对象
- 启动线程
public class Thread01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread01 t1 = new Thread01();
Thread01 t2 = new Thread01();
// t1.run();
// t2.run();直接调用run方法是不会启动多线程的
//void start(),此方法让线程开始执行
t1.start();
t2.start();
}
}
- 为什么要重写run()方法?
- 因为run()是用来封装被线程执行的代码
- run()和start()方法的区别?
- run():封装线程执行的代码,直接调用相当于普通方法的调用
- start():启动线程,然后由JVM调用此线程的run()方法
设置和获取线程名称
-
Thread类中设置和获取线程名称的方法
-
void setName(String name) :改变该线程的名称等于参数 name
-
String getName() :返回此线程的名称
-
通过构造方法也可以设置线程名称
-
Thread01(String name){ super(name);//通过super调用父类有参构造方法实现有参构造直接改名字 } -
获取main()方法所在的线程名称:static Thread currentThread() 返回当前正在执行的线程对象的引用
-
public static void main(String[] args) {
// Thread01 t1 = new Thread01();
// Thread01 t2 = new Thread01();
//
// //setName
// t1.setName("等级");
// t2.setName("经验");
//以上为使用Thread类的无参构造方法来进行线程命名
//使用带参构造方法命名则需要在自己建立的进程类中创建有参构造来访问父类的带参构造命名
Thread01 t1 = new Thread01("等级");
Thread01 t2 = new Thread01("经验");
// t1.start();
// t2.start();
//main类没有继承Thread所以无法使用getName获取main
//可以用(类名.方法名)直接调用Thread类中的方法先获取线程对象,然后再调用getName方法
//static Thread currentThread() 返回当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());//返回的是main
}
线程调度
public static void main(String[] args) {
Thread01 t1 = new Thread01();
Thread01 t2 = new Thread01();
Thread01 t3 = new Thread01();
t1.setName("等级");
t2.setName("经验");
t3.setName("技能");
//获取线程的优先级
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
// System.out.println(t3.getPriority());//都默认是5
//更改线程的优先级
System.out.println(Thread.MAX_PRIORITY);//最高优先级是10
System.out.println(Thread.MIN_PRIORITY);//最低优先级是1
System.out.println(Thread.NORM_PRIORITY);//默认优先级是5
t1.setPriority(2);
t2.setPriority(10);
t3.setPriority(3);
t1.start();
t2.start();
t3.start();
//优先级只是决定线程抢到时间片的几率并不是优先级越高就越先运行
}
- 优先级只是决定线程抢到时间片的几率并不是优先级越高就越先运行,默认是5,范围1-10
线程控制
-
static void sleep(long millis)
@Override public void run() { for (int i = 0; i < 500; i++) { System.out.println(getName()+":"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } -
执行后线程会以每毫秒间隔执行(这里是间隔1s)
- void join()
public static void main(String[] args) throws InterruptedException {
ThreadSleep t1 = new ThreadSleep("张三");
ThreadSleep t2 = new ThreadSleep("李四");
ThreadSleep t3 = new ThreadSleep("王五");
t1.start();
t1.join();
t2.start();
t3.start();
}
- 执行多线程后,只有t1对象执行完全部线程才会轮到t2,t3抢夺
- void setDaemon(boolean on)
public static void main(String[] args) {
ThreadSleep t1 = new ThreadSleep("张三");
ThreadSleep t2 = new ThreadSleep("李四");
//设置当前main类为主线程
Thread.currentThread().setName("老大");
//小弟守护老大,t1和t2设为守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
//当老大走完进程死亡后,守护线程失去了守护目标则也会过会线程死亡
}
- 当主进程死亡后,守护线程失去了守护目标则也会过一会也会线程死亡
线程的生命周期
多线程的实现方式---实现Runnable接口
- 方法2:实现Runnable接口
- 定义一个类实现Runnable接口
- 在类中重写run()方法
- 创建类的对象
- 创建Thread类的对象,把自创类的对象作为构造方法的参数
- 启动线程
public class Thread01 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
//可以通过这种方法获取该类默认名称
}
}
}
public static void main(String[] args) {
//创建一个自创类的对象
Thread01 t1 = new Thread01();
//创建Thread类的对象,把上面的自创类对象作为构造方法的参数
//Thread(Runnable target)
// Thread th1 = new Thread(t1);
// Thread th2 = new Thread(t1);
//Thread(Runnable target,String Name),能同时命名
Thread th1 = new Thread(t1,"张三");
Thread th2 = new Thread(t1,"李四");
//启动线程
th1.start();
th2.start();
}
- 相比于继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
线程同步
实例:买票
-
第一次实操出现问题
-
相同的票出现多次,原因是3个进程几乎同时进入run(),在还没--前完成了100的文字输出
-
出现了负数的票
-
同步代码块解决数据安全问题
-
为什么会出现问题(判断多线程程序是否有数据安全问题的标准)
- 是否是多线程环境
- 是否有共享环境
- 是否有多条语句操作共享数据
-
如何解决
- 基本思想:让程序没有安全问题的环境
- 针对第三点进行
-
实现
- 把多条语句操作共享数据的代码锁起来,让任意时刻只有一个线程执行
- java提供了同步代码块来实现
-
格式
synchronized(任意对象){ 多条语句操作共享数据的代码 }- synchronized(任意对象)相当于给代码加锁
public class SellTicket implements Runnable {
private int tickets = 200;
private Object ob = new Object();//建立一个object对象
@Override
public void run() {
while (true) {
//此时一开始tickets有100
//st1,st2,st3开始多线程运行争抢cpu执行
//假设st1抢到了执行权
//即使st2或者3抢到的执行权也要在代码块外面等
synchronized (ob) {
//t1进来后就会把代码锁住
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,目前剩余" + tickets + "张票。");
//tickets = 99
}
}
}
}
}
public static void main(String[] args) {
//创建买票类的对象
SellTicket st = new SellTicket();
//创建Thread的对象,加入买票对象实现线程
Thread st1 = new Thread(st,"1号窗口");
Thread st2 = new Thread(st,"2号窗口");
Thread st3 = new Thread(st,"3号窗口");
st1.start();
st2.start();
st3.start();
}
同步代码块的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多的适合因为每个线程都会去判断同步上的锁,所以是很消耗资源的,无形中会降低程序的运行效率
同步方法解决数据安全问题
同步方法
-
就是将synchronized关键字加到方法上
-
格式
修饰符 synchronized 返回值类型 方法名(方法参数){ }
同步方法的锁的对象
- this
同步静态方法
- 就是把synchronized关键字加到静态方法上
同步静态方法的锁对象
- 类名.class
public class SellTicket implements Runnable {
private int tickets = 200;
private Object ob = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if(x%2==0) {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,目前剩余" + tickets + "张票。");
}
}
}else{
sellTickets();
}
x++;
}
}
private synchronized void sellTickets() {//静态同步方法在synchronized前加static
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,目前剩余" + tickets + "张票。");
}
}
}
线程安全的类
-
以上三种线程安全方法都是自带synchronized修饰的
-
但是第二的vector和hashtable在多线程环境中不使用,被替代了
-
Collection集合下的synchronizedList(List
list)对list集合进行包装成线程安全集合使用 List<String>list = Collections.synchronizedList(new ArrayList<String>())
-
Lock锁
- 虽然我们可以理解同步代码块和方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
public class SellTicket implements Runnable {
private int tickets = 200;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,目前剩余" + tickets + "张票。");
}
}finally {
lock.unlock();
}//由于循环里的代码可能会出错导致unlock操作无法执行,使用trycatch包住把unlock放进finally这样即使出错unlock也能执行
}
}
}
生产者和消费者案例
概述
案例步骤
首先是奶箱类
public class MilkBox {
//定义一个成员变量,表示第x瓶奶
private int milk;
//再定义一个奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) {
//先判断奶箱的状态,如果有牛奶的话,等待消费者取奶
if (state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没牛奶了,就生产牛奶
this.milk = milk;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
//生产完后改变奶箱的状态
state = true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get() {
//判断奶箱的状态,如果没有牛奶则等待牛奶来
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶了,则取奶
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户拿到第" + this.milk + "瓶奶");
//取完奶后更改奶箱状态
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
生产者
public class Productor implements Runnable{
private MilkBox box;
public Productor(MilkBox box) {
this.box = box;
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
box.put(i);
}
}
}
消费者
public class Consumer implements Runnable{
private MilkBox box;
public Consumer(MilkBox box) {
this.box = box;
}
@Override
public void run() {
while (true){
box.get();
}
}
}
执行类
public class Take {
public static void main(String[] args) {
//创建奶箱区域
MilkBox box= new MilkBox();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为要使用奶箱存储牛奶
Productor pr = new Productor(box);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为要使用箱子来取奶
Consumer cs = new Consumer(box);
//创造两个线程
Thread t1 = new Thread(pr,"送奶工");
Thread t2 = new Thread(cs,"消费者");
//启动线程
t1.start();
t2.start();
}
}

浙公网安备 33010602011771号