1
大多数的程序语言只能循序运行单独一个程序块,但无法同时运行不同的多个程序块。Java的“多线程”恰可弥补这个缺憾,它可以让不同的程序块一起运行,如此一来可让程序运行更为顺畅,同时也可达到多任务处理的目的
1 新建线程
1.1 继承Thread类
package cn.sz.gl.no5;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("执行了线程的run方法_");
}
}
package cn.sz.gl.no6;
public class Test {
public static void main(String[] args) {
MyRunnable mra = new MyRunnable();
MyRunnable mrb = new MyRunnable();
MyRunnable mrc = new MyRunnable();
MyRunnable mrd = new MyRunnable();
MyRunnable mre = new MyRunnable();
new Thread(mra).start();
new Thread(mrb).start();
new Thread(mrc).start();
new Thread(mrd).start();
new Thread(mre).start();
}
}
1.2 实现Runnable接口
package cn.sz.gl.no6;
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我的线程...");
}
}
package cn.sz.gl.no6;
public class Test {
public static void main(String[] args) {
MyRunnable mra = new MyRunnable();
MyRunnable mrb = new MyRunnable();
MyRunnable mrc = new MyRunnable();
MyRunnable mrd = new MyRunnable();
MyRunnable mre = new MyRunnable();
new Thread(mra).start();
new Thread(mrb).start();
new Thread(mrc).start();
new Thread(mrd).start();
new Thread(mre).start();
}
}
1.3 实现Callable接口
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
package Thread;
import java.util.concurrent.*;
public class TestThread {
public static void main(String[] args) throws Exception {
testCallable();
}
public static void testCallable() throws Exception {
Callable callable = new MyThreadCallable();
FutureTask task = new FutureTask(callable);
new Thread(task).start();
System.out.println(task.get());
Thread.sleep(10);//等待线程执行结束
//task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
//get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
}
}
class MyThreadCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("通过实现Callable,线程号:" + Thread.currentThread().getName());
return 10;
}
}
1.4 情况A
在以上的代码中,线程一般为情况A,b,线程创建后使用start()函数,在就绪状态下各个线程互相抢夺CPU后进人运行状态.在运行状态下,有可能CPU调度使进程失去了执行权,调入了就绪状态,线程一旦完成会删除线程,进入死亡状态.
2.其他阻塞
Thread.sleep(毫秒数),让线程休眠,时间到,自动唤醒并继续执行
join():当前线程暂停执行,新加入的线程开始执行,当新线程执行完之后,再执行当前线程
可以使这些代码阻塞进程的运行,使部分的进程按顺序执行
Mythread3 t3 = new Mythread3();
t3.start();
Thread.sleep(1000);
Mythread3 t4 = new Mythread3();
t4.start();
Mythread3 t5 = new Mythread3();
t5.start();
Mythread3 t6 = new Mythread3();
t6.start();
Mythread3 t7 = new Mythread3();
t7.start();
在这种情况下由于主线程执行后,1秒内仅有t3一个进程,大概t3会第一个被执行
3.同步阻塞
处理多线程的问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问该对象的线程进入这个对象的等待池中,形成队列,等待前面线程使用完成,下一个线程再使用
在Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性;
每个对象都对应于一个可称为"互斥锁"的标记,这个标记保证在任一时刻,只能有一个线程访问对象;
用关键字synchronized给对象加互斥锁.
3.1 同步代码块
synchronized放在对象前面限制一段代码的执行,其语法如下:
Object obj = new Object();
...
synchronized(this){//this被加锁,任何其他要锁this的方法和代码块被阻塞.
需要同步的代码;
}
...
synchronized(obj){//obj被加锁,任何其他要锁obj的代码块被阻塞.
需要同步的代码;
}
new Thread(()->{
while (true) {
synchronized (lock) {
System.out.println("----------------------start");
System.out.println("111111111111111");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("----------------------end");
}
}
},"t1").start();
3.2 同步方法
同步非静态方法:synchronized放在方法声明中,表示整个方法为同步方法,锁定this对象,如果有一个线程进入了该方法,其他线程要想使用当前this对象的任何同步方法,都必须等待前一个线程执行完该同步方法之后
如下:
public synchronized void method1(){
…
}
public synchronized void method2(){
…
}
public synchronized void method3(){
…
}
同步static方法: synchronized放在static方法声明中,表示锁定该类的class对象(xxx.class,是Class类型的,是描述一个类的信息的对象)
如果有一个线程进入了该方法,其他线程要想使用当前类中的任何同步静态方法,都必须等待前一个线程执行完该同步方法之后;其他非同步方法及非静态的同步方法的执行不受影响
如下:
public synchronized static void method1(){
…
}
public synchronized static void method2(){
…
}
注意:
当前线程调用本同步方法时,其他线程是不允许调用**本对象**中其他同步方法
当前线程在调用本同步静态方法时,其他线程是不能够调用本类中的其他同步静态方法的,但是可以调用本类中的其他非同步方法或者非静态同步方法
对于同步静态方法,此时锁定的是对应的类型(类名.class),所有属于该类型的对象,都在其锁定范围内,此时可以实现方法的同步调用
线程同步的引发的问题:
* 一个线程持有锁会导致其他所有需要该锁的线程阻塞
package com.shadow.test.thread;
import java.sql.Time;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 锁的使用
*
* 7个程序员和一个老板的故事
*/
public class TestWait {
Lock lock = new Lock();
boolean isMoney =false;
boolean isTime =false;
public static void main(String[] args) throws InterruptedException {
TestWait tw = new TestWait();
tw.init();
}
public void init() throws InterruptedException {
new Thread(()->{
jackRun();
},"jack").start();
new Thread(()->{
roseRun();
},"rose").start();
// TimeUnit.MILLISECONDS.sleep(100);//保证jack线程先启动---不能百分百保证
// for (int i = 1; i < 6; i++) {
// new Thread(()->{
// run();
// },"t"+i).start();
// }
TimeUnit.SECONDS.sleep(3);
new Thread(()->{
synchronized (lock) {
System.out.println(currentTimeAndThreadName()+" 愿意给钱");
isMoney = true;
//把所有waitSet当中的线程全部唤醒起来--->虚假唤醒
lock.notifyAll();
//lock.notify();//随机唤醒一个waitSet当中的线程
}
},"boss").start();
}
public static String currentTimeAndThreadName(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss.SSS");
String format = simpleDateFormat.format(new Date());
return format+"-"+Thread.currentThread().getName()+" ";
}
public void roseRun() {
System.out.println(currentTimeAndThreadName() + "rose 来加班了");
synchronized (lock){
while (!isTime){//条件不满足---没有调休
System.out.println(currentTimeAndThreadName()+" 条件不满足");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(currentTimeAndThreadName()+" rose 醒来了");
}
System.out.println(currentTimeAndThreadName()+" rose 干活了");
System.out.println(currentTimeAndThreadName()+" rose 线程结束了");
}
}
public void jackRun() {
System.out.println(currentTimeAndThreadName() + "jack 来加班了");
synchronized (lock) {
while (!isMoney) {//没钱
//休息
try {
System.out.println(currentTimeAndThreadName()+" 条件不满足");
//sleep不会释放
//TimeUnit.SECONDS.sleep(60*60);
lock.wait();//让当前线程阻塞 主动调用一个方法才能醒来
//释放锁
System.out.println(currentTimeAndThreadName() + "jack 醒来了");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(currentTimeAndThreadName() + " jack 干活了有钱了");
System.out.println(currentTimeAndThreadName() +"jack 线程结束了");
}
}
//加班的方法
public void run() {
synchronized(lock) {
System.out.println(currentTimeAndThreadName() + "加班------");
}
}
}
如果代码使用了TimeUnit.SECONDS.sleep(60*60);jack拿到锁后,进入其他阻塞状态,不会释放锁,他人无法获取锁,只能等jack休息完才能工作.
* 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题,所以一般不会使用同步方法,此时方法内的所有内容运行将如单线程一般,毫无多线程的优点
* 线程同步可能会造成死锁