目录
一、线程创建
三、获取线程名称
五、线程中断
六、 守护线程
七、不安全线程
八、编辑线程安全
十、死锁
线程创建
简单的多线程实现:继承Thread的两种方式
第一种:直接继承Thread,Thread就是程序中的线程,继承了Thread的类就是线程类,继承之后需要重写run方法,run方法就是线程要执行的任务方法
package com.java;
public class MyThread extends Thread{
@Override
public void run(){
// 这是一条新的执行路径
// 这个路径的触发方式不是调用run方法,而是调用Thread的对象调用strart方法
for(int i =0;i<10;i++){
System.out.println("每个人的花期不同,不必焦虑有人比你提前拥有: "+i);
}
}
}
main方法里的线程是主线程,其他是分支线程,分支线程和主线程是并发执行的,因为java线程执行的抢占式线程,交替执行,所以每次执行的结果不同。
package com.java;
public class Text {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();//分支线程
for (int i = 0; i < 10; i++) {
System.out.println("夜色难免黑暗,前行必有曙光"+i);
}
}
}
执行结果第一次和第二次
ajva中该程序的执行流程,注意:子线程中任务调用方法都在子线程中执行,每个线程都有自己的栈空间,共用一份堆内存。
通过匿名内部类实现Thread
第二种 :通过匿名内部类实现Thread ,使用匿名类简化了代码,匿名类:创建一个类不起名字但是指定了他的父亲如本例子:new Thread(){,new一个没名字的类但指定了他的父亲是Thread
package com.java;
public class Ni_Ming {
public static void main(String[] args) {
// 使用匿名内部类直接实现Thread
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Ni_Ming"+i);
}
}
}.start();
for(int i=0;i<10;i++){
System.out.println("Ni"+i);
}
}
}
结果:
常用线程实现方法:runnable接口
实现 runnable与继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行先同任务的情况
- 可以避免单继承的局限性
- 任务与线程本身是分离的,提高了程序的健壮性
- 后继学习的线程池技术,接受runnable类型的任务,不接受Thread类型的线程。
实现接口runnable,实现之后需要重写run方法,run方法就是线程要执行的任务方法
package Runnable;
public class Text {
public static void main(String[] args) {
// 创建一个任务对象
MyRunnable r = new MyRunnable();
// 借助Thread 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
// 执行这个线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("生活才能亮晶晶"+i);
}
}
}
package Runnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("心里藏着小星星"+i);
}
}
}
执行结果:依旧是交替执行
Callable接口实现线程创建
Callable接口和Runnable接口的不同之处:
1.Callable规定的方法是call,而Runnable是run
2.call方法可以抛出异常,但是run方法不行
3.Callable对象执行后可以有返回值,运行Callable任务可以得到一个Futurer对象,通过Future对象可以了解任务执行情况,可以调用方法取消任务的执行.
多线程的实现步骤:
1.创建一个线程,创建Callable的实现类MyCallable,并且重写call方法
class MyCallable implements Callable<Integer> { public Integer call() throws Exception {} } 2.主线程创建得到FutureTask对象和Callable对象: Callable<Integer> c = new MyCallable(); FutureTask <Integer> f = new FutureTask<>(c); 3.获取返回值 Integer j = f.get();
package Callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo_1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask <Integer> f = new FutureTask<>(c);
new Thread(f).start();
//没有加入get之前,子线程和主线程同步执行
//加入之后先执行子线程-获取返回值-在输出主线程
Integer j = f.get();
System.out.println("返回值:"+j);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = " + i);
}
return 100;
}
}
}
加入get()结果 未加入get()结果
获取线程名称
Thread可以直接通过new创建线程
- new Thread() :创建一个没有任务的线程
- new Thread(Runnable target):创建一个有任务的线程
- new Thread(Runnable target,String name):创建一个有任务和名称的线程
start():启动线程
currentThread()该方法可以获得当前正在执行的线程
getName()获取线程名称
package Thread;
//如何获取线程名称
public class Demo_1 {
public static void main(String[] args) {
// 获取主线程的名称
System.out.println(Thread.currentThread().getName());
//不指定线程名称创建子线程
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
// 指定线程名称创建子线程
new Thread(new MyRunnable(), "找不到答案就找自己").start();
// 不使用strat()启动方式,用接收的方法
Thread t = new Thread(new MyRunnable());
t.setName("多反思线程");
t.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
// currentThread()该方法可以获得当前正在执行的线程
// 获取当前线程的名称
System.out.println(Thread.currentThread().getName());
}
}
}
运行结果
线程休眠sleep
sleep(毫秒)
sleep(毫秒,纳秒)
package Sleep;
public class Sleerly {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println( i);
Thread.sleep(1000);
}
}
}
结果:一秒输出一个数
线程中断
线程中断就是停止线程,告诉线程你该死了,一个线程是一个独立的执行路径,是否应该中断(结束)应该由自身决定,自杀的方法直接在run方法中return;
设置中断标记,但线程不死亡
package ZhongDuan;
public class Demo_2 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// printStackTrace()方法的意思是:在命令行打印异常信息在程序中出错的位置及原因。
e.printStackTrace();
}
}
// 给线程t添加中断标记,只要主线程执行完毕,线程t就会被中断。
t.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("发现了中断标记,但是我们不死亡");
}
}
}
}
}
结果:因为没有return,所以线程不会自杀结束,发现中断标记后继续执行
设置中断标记,线程死亡
package ZhongDuan;
public class Demo_3 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 给线程t添加中断标记,只要主线程执行完毕,线程t就会被中断。
t1.interrupt();
}
public static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("发现了中断标记,我们自杀死亡");
return;
}
}
}
}
}
结果:线程最后return,主线程结束子线程也会自杀结束
守护线程
线程分为守护线程和用户线程:
用户线程:包括主线程和子线程,当一个进程不包含任何的存活的用户线程时结束。
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程程序自动死亡,依附用于一个户程序。
setDaemon(true):将线程设置为守护线程
package ShouHU;
import ZhongDuan.Demo_3;
public class Demo_1 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.setDaemon(true);
// 将t1设置为守护线程,主线程结束,t1也会结束
t1.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//
e.printStackTrace();
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结果:将子线程t1设置为守护线程,因为守护线程依赖于用户线程,所以当主线程(这里边是用户线程)执行完毕,守护线程(t1)也不在执行。
不安全线程
原因:多个线程同时执行去争抢一个数据,导致某个数据判断时和使用时不一样,如下例:判断count>0进入循环,但在执行循环内的count--时,count=0
该示例是一个简陋的售票代码,共有5张票,创建3个线程相当于3个售票窗口。三个窗口抢票买,设置大于0的循环,但最后会发现有负票的情况出现,此时就发生了线程不安全的情况。
package Unsafety;
public class Demo_1 {
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
// 票数
private int count =5;
@Override
public void run() {
while (count>0) {
System.out.println("真在准备买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("票数剩余:" + count);
}
}
}
}
结果
产生负票原因
线程安全
线程安全就是加锁排队执行,一个线程在执行时其他线程不得插足。
上述不安全解决方法:
线程安全 1-同步代码块:
- 线程同步关键字:synchronized
- 同步代码块格式: synchronized(锁对象) {} //任何对象皆可为锁对象
package Saft;
import Unsafety.Demo_1;
public class TongBu {
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
// 票数
private int count =20;
private Object o = new Object();
@Override
public void run() {
while (true) {
// 三个线程看同一把锁,才能实现排队,但效率降低
// 将if语句加锁排队执行
synchronized (o) {
if (count > 0) {
//若锁放在启动处代表一个线程一把锁无法实现排队
// synchronized (o)
System.out.println("真在准备买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"票数剩余:" + count);
}else{
break;
}
}
}
}
}
}
结果:0线程比其他线程多是因为0线程首先执行,刚走出循环就又被打上锁标记所以比其他线程抢得快。
线程安全 2-同步方法:
在方法中加入关键字 synchronized ,例如 public synchronized boolean sale(){
如果一个类里边有十个同步方法,其中一个执行其他都不执行,因为他们都是同一把锁(this)
package Saft;
//线程安全-同步方法
public class Method {
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
private int count =10;
private Object o = new Object();
@Override
public void run() {
while (true) {
boolean flag = sale();
if(!flag){
break;
}
}
}
// 同步代码方法
public synchronized boolean sale(){
//锁的对象就是this,如该示例锁就是run
// 如果被static修饰,锁就是类名.class比如Ticket.class
if (count > 0) {
System.out.println("真在准备买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"票数剩余:" + count);
return true;
}
return false;
}
}
}
结果:排队执行
线程安全 3-显式锁:
隐式锁:同步方法和同步代码块都属于隐式锁。怎么锁不用管,只要把格式写上他会自己锁自己解开
显式锁:自己创建锁对象,自己锁自己解开。显示锁:lock,子类:ReentrantLock
显式锁和隐式锁的区别是面试容易问的,可以上网搜一下整理一下
package Saft;
import Unsafety.Demo_1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class XianShi {
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
// 票数
private int count =10;
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
// 上锁,将if块锁住
l.lock();
if (count > 0){
System.out.println("真在准备买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("票数剩余:" + count);
}
// 解锁
l.unlock();
}
}
}
}
结果:排队执行
公平锁和非公平锁
公平锁:有一个先来先到排队。实现:显式锁中子类ReentrantLock(true),穿一个参数TRUE即可。
非公平锁:同步代码块、同步方法、显式锁都是非公平锁。锁一旦解开大家一块抢。
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,事务T1在等待T2释放,T2在等待T1事务释放,导致T1和T2永远不能结束的现象。
死锁例子 :罪犯说话警察回应,调用罪犯说话方法时传入了警察对象当罪犯说完say执行完后在调用警察对象p的回应方法但下面的P.say(c)方法里的在等待c完全执行完才释放警察说话罪犯回应,调用警察的说话方法时传入最反对向 当警察say完后在调用罪犯对象c的回应方法 但上面的c.say(p)方法里的在等待p完全执行完才释放,二者相互等待对方释放才能执行就产生了死锁
package SiSuo;
public class Demo_1 {
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
// 创建一个子线程,让警察说话罪犯回应
new MyThred(c,p).start();
/*罪犯说话警察回应,调用罪犯说话方法时传入了警察对象
* 当罪犯说完say执行完后在调用警察对象p的回应方法,产生锁
*但下面的P.say(c)方法里的在等待c完全执行完才释放
* */
c.say(p);
}
static class MyThred extends Thread{
private Culprit c;
private Police p;
public MyThred(Culprit c,Police p){
this.c = c;
this.p = p;
}
/*
* 该线程是警察要做的事情,警察说话
*/
@Override
public void run(){
/*警察说话罪犯回应,调用警察的说话方法时传入最反对向
* 当警察say完后在调用罪犯对象c的回应方法,产生锁
* 但上面的c.say(p)方法里的在等待p完全执行完才释放
*/
p.say(c);
}
}
// 罪犯类
static class Culprit {
public void say(Police p) {
System.out.println("罪犯:你放了我,我放了人质");
}
//罪犯的回应语句
public void fun() {
System.out.println("罪犯:人质在这,我走了");
}
}
//警察类
static class Police {
public void say(Culprit c) {
System.out.println("警察:你放了人质,我放过你");
}
//警察的回应语句
public void fun() {
System.out.println("警察:你走吧,人质我救走了");
}
}
}
结果
死锁的解决方法:尽可能避免死锁的产生,在任何有可能产生锁的方法里不要再调用另外一个方法让另一个所产生。也就是这个方法已经产生一个锁了就不要找其他产生锁的方法。