Java第十八天(三)
线程
并发(线程同步)
采用实现Runable接口的方式实现
由于是使用实现Runnable接口的方式实现,所以在获取线程名时不能直接获取,需使用Thread类的静态方法。
当只有一个代码块使用一把锁时
线程类
public class SealTicketsThread implements Runnable{
// 由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
private int ticketsCount = 50;
// 由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
// 创建同步锁锁的对象
private Object obj = new Object();
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
// 这里使用同步锁将并发代码锁起来
synchronized (obj) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
break;
}
}
}
}
}
测试类
public class SealTicketsTest {
public static void main(String[] args) {
/**
* 创建售票类的对象,相当于是票
*/
SealTicketsThread sth1 = new SealTicketsThread();
// 这里使用支持自定义线程名的Thread构造器,相当于是窗口
Thread th1 = new Thread(sth1,"窗口1");
Thread th2 = new Thread(sth1,"窗口2");
Thread th3 = new Thread(sth1,"窗口3");
Thread th4 = new Thread(sth1,"窗口4");
// 启动线程
th1.start();
th2.start();
th3.start();
th4.start();
}
}
当不同的代码块使用同一把同步锁时,不会发生并发异常。
线程类
/**
* 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
*/
public class SealTicketsThreadSameLock implements Runnable{
// 由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
private int ticketsCount = 500;
// 由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
// 创建同步锁锁的对象
private Test2 test2 = new Test2();
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
int i = 0;
// 考虑奇偶情况来分别执行不同的代码块
if(i%2 == 0) {
// 这里使用同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
break;
}
}
}else {
// 这里使用相同的同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
}
}
}
i++;
}
}
}
class Test2{
}
测试类
public class SealTicketsTest {
public static void main(String[] args) {
/**
* 创建售票类的对象,相当于是票
*/
SealTicketsThreadSameLock sth = new SealTicketsThreadSameLock();
// 这里使用支持自定义线程名的Thread构造器,相当于是窗口
Thread th1 = new Thread(sth,"窗口1");
Thread th2 = new Thread(sth,"窗口2");
Thread th3 = new Thread(sth,"窗口3");
Thread th4 = new Thread(sth,"窗口4");
// 启动线程
th1.start();
th2.start();
th3.start();
th4.start();
}
}
针对上面的代码我们可以将第二个代码块使用方法封装起来。
注意:当同步锁不是静态成员变量时,将不能被所有对象共享。票数同理。
package com.threadExampleImplementsRunnable;
/**
* 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
*/
public class SealTicketsThreadSameLock implements Runnable{
// 由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享
// 但不被对象共享,所以我们要将它声明为静态的,这样我们声明的方法才能使用这个数据
private static int ticketsCount = 500;
// 由于使用实现Runable接口的方式,以至于同步锁对象被线程共享
// 但不被对象共享,所以我们要将它声明为静态的,这样我们声明的方法才能使用这个数据
// 创建同步锁锁的对象
private static Test2 test2 = new Test2();
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
int i = 0;
// 考虑奇偶情况来分别执行不同的代码块
if(i%2 == 0) {
// 这里使用同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
break;
}
}
}else {
// 调用使用方法封装的代码块
saleTickets();
}
i++;
}
}
// 声明一个方法,将第二个代码块封装起来
public static void saleTickets() {
// 这里使用相同的同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
}
}
}
}
class Test2{
}
当synchronized修饰非静态方法时,虽然我们不能指定锁对象,但是其同步锁对象为当前对象。
线程类
public class SealTicketsThread implements Runnable{
// 由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
private int ticketsCount = 200;
// 由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
// 创建同步锁锁的对象
private Object obj = new Object();
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
// 这里使用同步锁将并发代码锁起来
synchronized (obj) {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");`在这里插入代码片`
}else {
System.out.println(Thread.currentThread().getName()+"票已售完!");
break;
}
}
}
}
}
测试类
public class SealTicketsTest {
public static void main(String[] args) {
/**
* 创建售票类的对象,相当于是票
*/
SealTicketsThreadStaticMethod sth = new SealTicketsThreadStaticMethod();
// 这里使用支持自定义线程名的Thread构造器,相当于是窗口
Thread th1 = new Thread(sth,"窗口1");
Thread th2 = new Thread(sth,"窗口2");
Thread th3 = new Thread(sth,"窗口3");
Thread th4 = new Thread(sth,"窗口4");
// 启动线程
th1.start();
th2.start();
th3.start();
th4.start();
}
}
线程的休眠(sleep方法)与同步锁
线程的休眠是很必要的,当代码在服务器端运行时,为了减少服务器的压力,我们需要去依照运行场景进行适当的休眠,如果休眠是在带有同步锁代码块中执行,那么线程在休眠时不会让出锁对象(带着同步锁进行休眠)。
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的
精度和准确性。
(当线程执行了该方法就会立刻让出CPU的使用权,使当前线程处于中断状态。经过指定的时间后,该
线程就重新进到线程队列中等待CPU资源,以便从中断处继续运行。)
线程类
public class ThreadSleepDemo implements Runnable{
@Override
public void run() {
while(true) {
try {
// 这里单位是毫秒,此句代码将产生异常,并且我们必须处理
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date());
}
}
}
测试类
public class ThreadSleepTest {
public static void main(String[] args) {
ThreadSleepDemo threadSleepDemo = new ThreadSleepDemo();
Thread t = new Thread(threadSleepDemo);
t.start();
}
}
线程间的通信(协调同步的线程)

前提:厂家生产货物,经销商出售货物,消费者购买货物,经销商不得囤积货物,一旦经销商有货物消费者就会购买。
当经销商有货物时,厂家则不生产并处于等待生产状态,消费者则开始购买。
当经销商没有货物时,经销商会给厂家发送需求信息,厂家在收到信息后则开始生产货物,并供给经销商。
注意:wait()方法与notify()方法成对出现!
货物类
/**
*货物类
*/
public class Goods {
// 声明货物名
private String goodsName;
// 声明货物状态
private boolean isExsit;
/**
* 对成员变量进行封装
*/
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public boolean isExsit() {
return isExsit;
}
public void setExsit(boolean isExsit) {
this.isExsit = isExsit;
}
}
厂家线程类
/**
*厂家线程类
*/
public class Factory implements Runnable{
// 由于货物是厂家线程与消费者的共享数据,所以要传递过来
private Goods goods;
public Factory(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
// 由于消费者实时消费,所以厂家也实时生产
while(true) {
// 当多个线程操作共享数据时,此块操作共享数据的代码必须使用同步锁
// 由于货物是厂家线程与消费者线程的共享数据,所以要使用同步锁
synchronized (goods) {
// 这里需要判断经销商是否有库存货物
if(goods.isExsit()) {
// 如果经销商有库存货物,厂家则不生产,并处于等待生产状态
// wait()方法属于Object类
try {
// 此时厂家的线程被挂起,变为阻塞状态
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果经销商没有货物,厂家则开始生产货物
System.out.println("厂家正在生产 "+goods.getGoodsName()+" 并供给经销商中.....");
// 此时货物已被生产并供给经销商完毕,所以更改货物状态并通知消费者开始购买
goods.setExsit(true);
goods.notify();
}
}
}
}
消费者线程类
/**
*消费者线程类
*/
public class Customer implements Runnable{
// 由于货物是厂家线程与消费者的共享数据,所以要传递过来
private Goods goods;
public Customer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
// 消费者实时消费
while(true) {
// 当多个线程操作共享数据时,此块操作共享数据的代码必须使用同步锁
// 由于货物是厂家线程与消费者线程的共享数据,所以要使用同步锁
synchronized (goods) {
// 这里需要判断经销商是否有库存货物
if(!goods.isExsit()) {
// 如果经销商没有库存货物,消费者则处于等待购买状态
// wait()方法属于Object类
try {
// 此时消费者的线程被挂起,变为阻塞状态
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果经销商有货物,消费者则开始购买
System.out.println("消费者购买了 "+goods.getGoodsName());
// 由于货物被消费者购买,所以货物状态变为无,并通知厂家开始生产
goods.setExsit(false);
goods.notify();
}
}
}
}
测试类
public class Test {
public static void main(String[] args) {
Goods goods = new Goods();
goods.setGoodsName("货物");
goods.setExsit(false);
Factory factory = new Factory(goods);
Customer customer = new Customer(goods);
Thread t = new Thread(factory,"厂家");
Thread t2 = new Thread(customer,"消费者");
t.start();
t2.start();
}
}
线程的优先级(set、getPriority()方法)
返回值类型 方法名 参数列表
void setPriority(int newPriority)
更改此线程的优先级。
---------------------------------------------------------------------------------
int getPriority()
返回此线程的优先级。
---------------------------------------------------------------------------------
| Modifier and Type(修饰符和类型) | Constant Field(常数域) | Value(值) |
|---|---|---|
| public static final int | MAX_PRIORITY(最大优先级) | 10 |
| public static final int | MIN_PRIORITY(最小优先级) | 1 |
| public static final int | NORM_PRIORITY(默认优先级) | 5 |
由上面的表格我们可以得出:我们在创建线程后若未设置优先级,那么该线程的优先级为默认优先级,也就是5;线程的最大优先级为10,最小的优先级为1。
线程的优先级不是绝对的,只是相对来说能够比其他低等级优先级的线程得到CPU的资源机会更多一些。
线程类
public class ThreadPriority extends Thread{
@Override
public void run() {
for(int i = 1;i<30;i++) {
System.out.println(this.getName()+"线程的第 "+i+" 次输出");
}
}
}
测试类
public class TestThreadPriority {
public static void main(String[] args) {
ThreadPriority tp = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
// 未设置线程优先级
System.out.println("未设置线程优先级:");
System.out.println(tp.getPriority());
System.out.println(tp2.getPriority());
System.out.println(tp3.getPriority());
tp.setPriority(10);
tp2.setPriority(2);
tp3.setPriority(3);
// 设置线程优先级后
System.out.println("设置线程优先级后:");
System.out.println(tp.getPriority());
System.out.println(tp2.getPriority());
System.out.println(tp3.getPriority());
tp.start();
tp2.start();
tp3.start();
}
}
加入线程(join方法)
void join()
等待这个线程死亡。
---------------------------------------------------------------------------------
使用join()方法的线程会被优先分配到CPU资源并开始执行线程,然后其他线程依次被分到CPU资源进行执行。
注意:只有在线程启动后才可以被分配到CPU资源。
线程类
public class ThreadJoin extends Thread{
@Override
public void run() {
for(int i = 1;i<30;i++) {
System.out.println(this.getName()+"线程的第 "+i+" 次输出");
}
}
}
测试类
public class TestThreadJoin {
public static void main(String[] args) {
ThreadJoin tp0 = new ThreadJoin();
ThreadJoin tp1 = new ThreadJoin();
ThreadJoin tp2 = new ThreadJoin();
// 这里要先启动线程
tp2.start();
tp0.start();
tp1.start();
// 设置线程tp2优先被分配到CPU资源,并执行其线程
try {
tp2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
等待线程(yield()方法)
使当前线程从运行阶段回到就绪阶段,目的是为了将CPU的资源让给其他线程,使线程执行得更均匀一些。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
---------------------------------------------------------------------------------
线程类
public class ThreadYield extends Thread{
@Override
public void run() {
for(int i = 1;i<30;i++) {
System.out.println(this.getName()+"线程的第 "+i+" 次输出");
// 暂停当前正在执行的线程对象,并执行其他线程。
Thread.yield();
}
}
}
测试类
public class TestThreadYield {
public static void main(String[] args) {
ThreadYield tp0 = new ThreadYield();
ThreadYield tp1 = new ThreadYield();
ThreadYield tp2 = new ThreadYield();
// 这里要先启动线程
tp0.start();
tp1.start();
tp2.start();
}
}
守护线程(setDaemon()方法)
void setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程。
---------------------------------------------------------------------------------
线程默认是非守护线程,非守护线程也称作用户(user)线程,一个线程调用void setDaemon(boolean on)方法可以将自己设置成一个守护(Daemon)线程。
当程序中的所有用户线程都已结束运行时,即使守护线程的run方法中还有需要执行的语句,守护线程也会立刻结束运行。即守护线程会随着主线程消亡而消亡。
我们可以使用守护线程做一些不是很严格的工作,线程的随时结束不会产生什么不良的后果。一个线程必须在运行之前自己是否是守护线程。
线程类
public class ThreadDaemon extends Thread{
@Override
public void run() {
for(int i = 1;i<30;i++) {
System.out.println(this.getName()+"线程的第 "+i+" 次输出");
}
}
}
测试类
public class TestThreadDaemon {
// 主方法也就是主线程
public static void main(String[] args) {
ThreadDaemon tp0 = new ThreadDaemon();
ThreadDaemon tp1 = new ThreadDaemon();
ThreadDaemon tp2 = new ThreadDaemon();
// 在启动线程前设置守护线程
tp0.setDaemon(true);
tp1.setDaemon(true);
tp2.setDaemon(true);
// 这里要先启动线程
tp0.start();
tp1.start();
tp2.start();
// 设置主线程在循环打印十次就结束运行
for(int i = 1;i<=3;i++) {
System.out.println(Thread.currentThread().getName()+"主线程运行的第 " + i+ " 次");
}
}
}
这篇博客深入探讨了Java中的多线程概念,包括通过实现Runable接口创建线程,线程同步避免并发异常,线程休眠与同步锁的使用,线程间的通信策略,线程优先级的设置和获取,join方法用于线程加入,yield方法用于线程等待,以及如何将线程设置为守护线程。通过实例代码详细阐述了每个主题的应用。
998

被折叠的 条评论
为什么被折叠?



