一、线程Thread类常用方法
(一)线程API之线程休眠
1、方法介绍
代码示例:
package com.offcn.demo01;
public class UseSleep {
public static void main(String[] args) {
new Thread("线程1"){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"-----上课好困---"+i);
}
}
}.start();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----学习java---"+i);
}
}
};
Thread t = new Thread(r,"线程2");
t.start();
}
}
(二)线程API之线程优先级
1、方法介绍
设置了线程优先级【线程的执行java遵循抢占式机制,不随着人的意志为转移,规律人为决定不了】,表现就是在一段时间内,开始的时候高优先级执行次数多点,结束的时候优先级低的执行的次数多一点
尽量的要前期执行优先级高的【不是百分百】也就是,不一定优先级高的线程先执行完
代码示例:
package com.offcn.demo01;
public class demo01 {
public static void main(String[] args) {
Thread t1 = new Thread("线程一"){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"---线程一---"+i);
}
}
};
t1.setPriority(Thread.MAX_PRIORITY);
Thread t2 = new Thread("线程二"){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"---线程二---"+i);
}
}
};
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
(三)线程API之线程礼让
1、方法介绍
代码示例:
package com.offcn.demo01;
public class Demo02 {
public static void main(String[] args) {
Thread t1 = new Thread("线程1"){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
Thread.yield();
}
System.out.println(getName()+"---线程一---"+i);
}
}
};
Thread t2 = new Thread("线程2"){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"---线程二---"+i);
}
}
};
t1.start();
t2.start();
}
}
(四)线程API之线程中断【对线程的休眠、等待等状态的中断】
1、方法介绍
代码示例:
package com.offcn.demo01;
import java.util.Scanner;
import static java.lang.Thread.sleep;
public class Demo03 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程");
}
};
t.start();
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
if(s.equals("Y")){
t.interrupt();
}else {
System.out.println("继续等待");
}
}
}
(五)线程API之后台线程【守护线程】
1、方法介绍
解释:
守护线程为了保护其他线程执行而存在的,为其他线程提供良好的运行环境,如果被保护的执行线程已经消失(任务结束),守护线程在不久的将来也会跟着消失
代码示例:
package com.offcn.demo01;
public class Demo04 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 366; i++) {
System.out.println("被守护线程"+i);
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
while(true){
System.out.println("守护线程");
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
}
}
二、多线程安全问题
(一)问题描述
1、买票的案例
案例需求:
某电影院目前正在上映一部电影,共100张票,而它由3个窗口卖票,设计一个影院卖票程序
实现步骤:
定义一个类Sell实现Runnable接口,里面定义一个成员变量:private int ticket=100;
在Sell类中重写run()方法实现卖票:
判断票数大于0,就卖票,并告知是哪个窗口卖的
卖了票之后,总票数要减1
票卖没了,线程停止
定义测试类,里面有main方法:
创建Sell对象
创建三个Thread类对象,把Sell对象作为构造方法的参数,并给树对应窗口名称
启动线程
代码示例:(注意这个案例只是为了证明多线程的安全问题)
package com.offcn.demo02;
public class Sell implements Runnable {
private int ticket = 100;
@Override
public void run() {
while(ticket > 0){
System.out.println(Thread.currentThread().getName()+"卖出"+ticket+"号票,剩余"+(ticket-1)+"张");
ticket--;
}
}
}
package com.offcn.demo02;
public class Demo01 {
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
2、案例中出现的问题
卖票出现了问题:
①相同的票出现了多次
②出现了负数的票
问题产生的原因:线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题【多线程在同时操作同一个数据时就发生安全问题】
3、解决方案:保证线程执行任务的原子(完整性)操作
原子操作就是不可分割的操作,例如:一个售票窗口在售票的过程中的代码就是一个不可分割的操作。
就是一条线程在操作某个任务的时候,一定要保证他的操作必须完全都结束,其他的线程才有机会做这个操作
体现:让cpu变得同步【有序】
处理方案:
①同步代码块
②同步方法
③Lock接口
(二)同步代码块
1、格式
synchronized(锁对象){
原子操作的代码;【有可能出现线程安全问题的代码】
}
2、说明
①锁对象可以是任何引用类型的对象,只要是一个java对象就可以当做锁对象
②锁对象一旦被确定下来要保证唯一性,锁对象只能有一份
3、同步代码块执行原理
一段代码一旦使用同步代码块之后, 线程只要进入同步代码块,就要先获取锁对象,只有拿到锁对象才能进入同 步代码块执行任务, 在这个线程执行任务期间,CPU可能会把资源切换到其他线程,即使其他线程也要进入这个 同步代码块,因为锁对象已经被之前的线程拿走了,它没有锁对象,依然进不来, 只能等之前线程执行完,把锁 对象归还,其他线程才有机会拿到锁对象,执行里面任务.
4、优缺点
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程执行任务时都要先判断是否有锁对象可用,拿锁对象,执行完之后把锁对象归还,这就是耗费资源的,无形之中降低程序的运行效率,这个弊端可以完全忽略
代码示例:(以卖票为例)
package com.offcn.demo03;
public class Sell implements Runnable {
private int ticket = 100;
@Override
public void run() {
while(ticket > 0){
synchronized ("A") {
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
ticket--;
}
}
}
}
}
package com.offcn.demo03;
public class Demo01 {
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
(三)同步方法
1、格式
修饰符 synchronized 返回值类型 方法名(方法参数){
方法体;
}
2、什么时候用
如果你发现你写的一个成员方法,这个方法内部所有的代码都使用同代码块给包裹了,并且使用的锁对象是 this的时候.就可以使用同步成员方法简化这个方法
package com.offcn.demo04;
public class Sell implements Runnable {
private int ticket = 100;
@Override
public void run() {
while(ticket > 0){
sell();
}
}
public synchronized void sell(){
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
ticket--;
}
}
}
package com.offcn.demo04;
public class Demo01 {
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
3、同步静态方法
与同步方法类似,在synchronized前加static,成员便来那个也要是静态的。
package com.offcn.demo05;
public class Sell implements Runnable {
private static int ticket = 100;
@Override
public void run() {
while(ticket > 0){
sell();
}
}
public static synchronized void sell(){
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
ticket--;
}
}
}
package com.offcn.demo05;
import java.util.Hashtable;
public class Demo01 {
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
4、两个同步方法的锁对象
①普通同步方法的锁对象是this
②静态同步方法的锁对象是 类名.class
(四)Lock锁
1、概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更加清晰的表达如何加锁和释放锁,jdk1.5之后提供了一个新的锁对象Lock
2、使用方法
代码示例:
package com.offcn.demo06;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Sell implements Runnable {
private int ticket = 100;
private Lock l = new ReentrantLock();
@Override
public void run() {
while(ticket > 0){
l.lock();
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "卖出" + ticket + "号票,剩余" + (ticket - 1) + "张");
ticket--;
}
l.unlock();
}
}
}
package com.offcn.demo06;
public class Demo01 {
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
三、线程死锁的概述以及现象演示
1、概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的锁资源,导致这些线程处于等待状态,无法继续执行
实际工作不要写出死锁,要规避死锁
代码示例:
package com.offcn.demo07;
public class SeeDeadLock {
private static final String L1 = "1";
private static final String L2 = "2";
public static void main(String[] args) {
new Thread("线程1"){
@Override
public void run() {
synchronized (L1){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (L2){
System.out.println("任务1");
}
}
}
}.start();
new Thread("线程2"){
@Override
public void run() {
synchronized (L2){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (L1){
System.out.println("任务2");
}
}
}
}.start();
}
}
四、线程状态
1、概述
线程生命周期,线程对象从生到死的一个过程,当线程被创建并启动以后,他既不是一启动就进入了执行状态,也不是一直处于执行状态,线程对象在不同时期有不同的状态
2、线程状态图示
在java代码中对线程的状态有更加详细的描述,是通过Thread类中,调用getState()此方法可以返回线程的各种状态,提供一个枚举类型描述线程的各种状态
3、
getState()获取线程当前的状态值