Thread
- 线程
- 启动线程
- 第一种创建线程
- 线程的第二种创建方式
- 使用匿名内部类完成线程的两种创建
- Thread API
- 线程的优先级
- 线程提供的静态方法
- 守护线程
- 用户线程和守护线程的区别体现在进程结束时
- 多线并发安全问题
- 同步块
线程
启动线程
启动线程:调用线程的start方法,而不是直接调用run方法
当线程调用start方法后就会纳入到线程调度器中统一调度.第一次获取时间片后会开始自动执行它的run方法
第一种创建线程
class MyThread1 extends Thread{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
}
class MyThread2 extends Thread{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("开门,查水表的!");
}
}
}
优点:
- 结构简单,便于使用匿名内部类形式创建
缺点:
-
存在继承冲突问题
当我们定义的类需要是一个线程时,我们要继承Thread,但是同时我们还需要继承另一个类来复用方法,
由于java是单继承的,这会导致出现了继承冲突问题 -
线程与线程要执行的任务存在了必然的耦合关系
定义一个线程的同时重写了run方法来定义线程的任务,这会导致线程和任务绑定在一起,没有办法最大程度的重用线程
线程的第二种创建方式
class MyRunnable1 implements Runnable{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("开门,查水表的!");
}
}
}
使用匿名内部类完成线程的两种创建
Runnable r2 = () -> {
for (int i = 0; i < 1000; i++) {
System.out.println("开门!查水表!");
}
};
Thread t2 = new Thread(r2);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
});
Thread API
线程的优先级
线程有10个优先级,分别用整数1-10表示
1为最低优先级,10为最高优先级,5为默认值
Thread定义了上下限的常量
- Thread.MAX_PRIORITY为最高优先级,对应整数10
- Thread.MIN_PRIORITY为最低优先级,对应整数1
- Thread.NORM_PRIORITY为默认优先级,对应整数5
优先级越高的线程获取时间片的次数越多,线程调用start方法后就纳入到线程调度器中被统一管理,线程没有主动索取时间片的能力,只能被动被分配
public static void main(String[] args) {
Thread min = new Thread(){
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("min");
}
}
};
Thread max = new Thread(){
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("max");
}
}
};
Thread norm = new Thread(){
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("nor");
}
}
};
min.setPriority(Thread.MIN_PRIORITY);
max.setPriority(Thread.MAX_PRIORITY);
min.start();
norm.start();
max.start();
}
线程提供的静态方法
- static void sleep(long millis)
该方法可以让执行该方法的线程主动进入阻塞状态指定毫秒,超时后线程会主动回到RUNNABLE状态再次开始并发
public static void main(String[] args) {
System.out.println("程序开始了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束了");
}
sleep方法要求必须处理中断异常InterruptedException
当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt方法被调用,那么就会立即中断其睡眠阻塞,此时sleep方法会立即抛出中断异常
public static void main(String[] args) {
Thread lin = new Thread("林永健"){
public void run(){
System.out.println(getName()+":刚美完容,睡一会吧...");
try {
Thread.sleep(300000000);
} catch (InterruptedException e) {
System.out.println(getName()+":干嘛呢!干嘛呢!干嘛呢!都破了相了!");
}
System.out.println(getName()+":醒了");
}
};
Thread huang = new Thread("黄宏"){
public void run(){
System.out.println(getName()+":大锤80!小锤40!开始砸墙!");
for(int i=0;i<5;i++){
System.out.println(getName()+":80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("咣当!!");
System.out.println(getName()+":大哥!搞定!");
lin.interrupt();//中断lin线程的睡眠阻塞
}
};
lin.start();
huang.start();
}
守护线程
我们正常创建出来的线程称为"用户线程"或"前台线程"。守护线程是通过用户线程调用setDaemon(true)在启动前设置转变而来的.守护线程一般也可以称为后台线程
用户线程和守护线程的区别体现在进程结束时
当进程中所有用户线程都结束时,进程就会结束,结束前会无差别杀死所有还在运行的守护线程
GC就是运行在守护线程上的
public static void main(String[] args) {
Thread rose = new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("rose:啊啊啊啊啊AAAAAAaaaaaaa....");
System.out.println("pia!");
}
};
Thread jack = new Thread(){
public void run(){
while(true){
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
rose.start();
jack.setDaemon(true);//设置为守护线程,必须在线程启动前进行设置否则会抛出异常
jack.start();
//while (true);//如果main方法不结束,jack是不会结束的
}//main方法执行完毕,主线程就结束了
多线并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致执行顺序出现混乱从而引起不良后果
临界资源:
操作该资源的完整过程同一时刻只能被单个线程进行
/**
* 使用当前类理解并发安全问题的产生
* 以银行取钱业务为例,如果多个人从同一个账号上取钱所出现的并发全问题.
*/
public class Bank {
private int account = 20000;
public boolean getMoney(int money) {
int account = getAccount();
if (account >= money) {//余额>=取款金额
account = account - money;
Thread.yield(); //主动放弃时间片目的模拟执行到这里时CPU时间片用完了
saveAccount(account); //重新记账
return true; //取款成功
}
return false; //取款失败
}
public void saveAccount(int account) {
this.account = account;
}
public int getAccount() {
return account;
}
}
public static boolean success1 = false;//表示t1线程是否取款成功
public static boolean success2 = false;//表示t2线程是否取款成功
public static int sum = 0;//记录一共测试了几轮
public static void main(String[] args) {
//实例化一个Bank
Bank bank = new Bank();
while(true) {
sum++;
Thread t1 = new Thread() {
public void run() {
success1 = bank.getMoney(20000);
}
};
Thread t2 = new Thread() {
public void run() {
success2 = bank.getMoney(20000);
}
};
t1.start();
t2.start();
try {
/*
当主线程执行完上述启动t1和t2的代码后,让主线程阻塞5毫秒(目的是等待t1和t2执行完毕)
*/
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(success1&&success2){//都为true则说明同时取款成功
System.out.println("漏洞产生,两个线程同时取款成功!总共尝试了"+sum+"次");
break;
}else{
bank.saveAccount(20000);//将银行账号重新存入20000
}
}
}
同步块
Java同步块是一种同步机制,它可以确保在同一时刻只有一个线程可以执行特定的代码块或方法。这种机制可以防止多个线程同时修改同一数据,从而防止出现竞态条件。
- 同步块可以更准确的控制需要多个线程同步执行的代码片段
同步块要求指定同步监视器对象,该对象选取有两个必要的要求:
- 必须是一个引用类型对象
- 多个需要同步执行该代码片段的线程看到的必须是"同一个"同步监视器对象
合适的锁对象选取:
在具有并发安全问题时,多个线程看到的是同一个对象,不存在并发安全问题时看到的则不是同一个对象.
选取原则:
通常选取临界资源作为同步监视器对象,即:抢谁就锁谁
语法:
- synchronized(同步监视器对象){
需要多个线程同步执行的代码片段
}
有效的缩小同步范围可以在保证并发安全的前提下提高并发效率
public static void main(String[] args) {
Shop shopA = new Shop();
Shop shopB = new Shop();
Thread t1 = new Thread("Tom"){
public void run(){
shopA.buy();
}
};
Thread t2 = new Thread("Jerry"){
public void run(){
shopB.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
/*
在方法上直接使用synchronized,那么同步监视器对象不可选,只能是this
*/
// public synchronized void buy(){
public void buy(){
try {
Thread t = Thread.currentThread();//获取运行buy方法的线程
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
synchronized (this) {
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}