多线程
前言:当我们打开一个网站时,不同部分的加载并不是先后出现的,是并行出现的,没有出现一个地方没加载完,别的地方就也加载不出来这种事。这个就是多线程并行运行。
当其中一个线程发生阻塞时,操作系统会自动执行新的线程保证cpu不会闲置???
实现方式1:
继承一个Thread类并重写其中的run()函数
对于想要实现的多线程逻辑直接写在run函数里面即可
class worker extends Thread {
@Override
public void run(){
for(int i=0;i<10;i++)
{
System.out.println("Helo!"+this.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
在main函数中实现两个线程并启动
package test;
import static java.lang.Thread.sleep;
class worker extends Thread {
@Override
public void run(){
for(int i=0;i<10;i++)
{
System.out.println("Helo!"+this.getName());
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
worker w1=new worker();
worker w2=new worker();
w1.setName("线程1");
w2.setName("线程2");
w1.start();
w2.start();
for(int i=0;i<10;i++)
{
System.out.println("这是主线程");
sleep(1000);
}
}
}
原本的main方法就是一个主线程,当执行到w1.start时就会新开一个线程,执行到w2.start时会再新开一个线程,start方法会自动调用run()方法。然后就会有三个线程在同时执行。
运行代码的输出结果如下图所示,可以看见三个线程在先后的运行,在cpu当中运行时则是一个一个线程进cpu运行输出结果,在外面看起来就是差不多同时。
实现方法2:
实现Runnable接口
用这个方法实现有多少种线程就要创建多少个Runnable的实现类,并且这个里面是没有setName方法。在开启线程时需要new一个实现类的实例作为参数传进Thread()里面,,然后调用start()方法,会自动运行run()函数。
要么也可以共用一个实例,上面继承的写法是每一个都要有一个实例。
共用一个实例更好实现下面的锁的同步机制
package test;
class worker1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + "thread-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class worker2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + "thread-2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
new Thread(new worker1()).start();
new Thread(new worker2()).start();
}
}
常用的API
start():开启一个线程
Thread.sleep(): 休眠一个线程
join():等待线程执行结束 //等当前线程执行结束后才会继续往下执行,前提是该线程已经开始执行了,也可以设定等待时间
interrupt():从休眠中中断线程 //给当前线程发送一个中断,至于线程如何处理就要看run函数的内部是怎么样的,这个方法只有在run函数内有抛出中断时才有影响。
setDaemon():将线程设置为守护线程。当只剩下守护线程时,程序自动退出,java中有一个垃圾回收机制,当所有用户的线程结束之后就要自动结束,守护线程就可以用于设置一些没用的线程,当别的有用的线程都结束了就自动结束这些无用的 线程。
在如下的代码当中w2设置为了守护线程,w1设置了一个中断结束,因此在w1结束的时候w2也会一起结束。
package test;
import static java.lang.Thread.sleep;
class worker extends Thread {
@Override
public void run(){
for(int i=0;i<10;i++)
{
System.out.println("Helo!"+this.getName());
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
break;
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
worker w1=new worker();
worker w2=new worker();
w1.setName("线程1");
w2.setName("线程2");
w2.setDaemon(true);
w1.start();
w2.start();
w1.interrupt();
}
}
输出如下所示
锁锁锁——锁锁world new new!
锁的必要性:对于一个整型变量cnt=5,现在有两个操作同时发生,一个是读,一个是写(自增1)
先读后写读到的就是5
先写后读,读到的就是6。正常情况下不能确定是哪一个结果,我们要的是哪一个结果也不能确定。
锁的意义在这里就体现了出来,当一个线程在对一个变量进行操作时不允许别的线程也同时过来操作。
在上面的多线程也可以实现这一点,使用join函数保证在当前线程结束前别的线程不会启动。
如在下面这个多线程实现类当中,所有实例都共享一个锁和一个cnt属性,一个线程在的执行cnt++之前都会先上锁,不让别的线程修改cnt的值,直到cnt获得了新值之后才会解锁。
class worker extends Thread {
private static final ReentrantLock lock=new ReentrantLock();
private static int cnt=0;
@Override
public void run(){
for(int i=0;i<10;i++)
{
lock.lock();
try{
cnt++;
}finally {
lock.unlock();
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
worker w1=new worker();
worker w2=new worker();
w1.setName("线程1");
w2.setName("线程2");
w2.setDaemon(true);
w1.start();
w2.start();
w1.join();
w2.join();
System.out.println(w1.getcnt());
}
}
上面代码执行输出如下
成功执行了20次
下面是没加锁的情况
在操作系统中使用pv操作实现
锁的语法糖
同步(Synchronized)
synchronized(对象) {代码}:当加上这个之后,任何想要访问这个对象的线程都会被阻塞。直到里面的代码执行完成。
写法1:将Synchronized加到代码块上
如下的代码就是锁住了一段代码的count属性,这个count参数是被所有worker实例线程共享的,直到该段代码执行完毕或锁被释放才会执行别的线程的这段代码
package test;
class Count{
public int cnt=0;
}
class worker extends Thread {
public final Count count;
public worker(Count count){
this.count=count;
}
@Override
public void run(){
synchronized (count) {
for (int i = 0; i < 10000; i++) {
count.cnt++;
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
Count count=new Count();
worker w1=new worker(count);
worker w2=new worker(count);
w1.setName("线程1");
w2.setName("线程2");
w2.setDaemon(true);
w1.start();
w2.start();
w1.join();
w2.join();
System.out.println(w1.count.cnt);
}
}
写法2:将Synchronized加到函数上(锁加到了this对象上)
如果是使用继承写法加到函数上的话像下面
class worker extends Thread {
public static Integer cnt=0;
private synchronized void work(){
for (int i = 0; i < 10000; i++) {
cnt = cnt + 1;
}
}
@Override
public void run(){
work();
}
}
输出结果是错误的
上面的等价于如下的写法
private void work(){
synchronized (this) {
for (int i = 0; i < 10000; i++) {
cnt = cnt + 1;
}
}
}
synchronized里面的参数时是当前这个线程的对象,因为有两个线程,两个对象,所以实际是不会互相影响的,所以在函数上加synchronized不适用于这种多个对象的线程
使用接口形式实现的同一个对象可以运行在多个不同的线程上,接口实现的多线程创建一个实例传进两个线程里面运行,这时候synchronized加在函数上面就可以提现其作用了。
package test;
class Worker implements Runnable {
public static int cnt = 0;
private synchronized void work() {
for (int i = 0; i < 100000; i ++ ) {
cnt ++ ;
}
}
@Override
public void run() {
work();
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
Worker worker = new Worker();
Thread worker1 = new Thread(worker);
Thread worker2 = new Thread(worker);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
wait与notify
一个是等待一段时间,一个是从等待中退出
needWait=true表示当前线程需要等待
下面代码的逻辑是先开启了五个线程,都被阻塞在了同一个地方,也就是下面的odject.wait()那里,然后使用一个新的线程来随机唤醒一个旧的线程,一个旧线程被唤醒会先输出一个被唤醒的信息,然后经过一秒再去唤醒其他随机一个线程。
也可以使用notifyAll()直接唤醒所有被睡眠的线程。
wait()当中可以传参进去,wait(1000)表示睡眠一秒就自动唤醒了。
package test;
class worker extends Thread {
private static final Object object=new Object();
private final boolean needWait;
public worker(boolean needWait){
this.needWait=needWait;
}
@Override
public void run() {
synchronized (object){
try {
if(needWait) {
System.out.println(this.getName()+"婴儿般的睡眠");
object.wait();//进入等待后就一直卡在这里
System.out.println(this.getName()+"被唤醒了");//直到的外部有操作来唤醒了
Thread.sleep(1000);
object.notify();
}else{
object.notify();//全部唤醒 ,单一个notify就是随便唤醒一个
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class test {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<5;i++)
{
worker worker=new worker(true);
worker.setName("thread-"+i);
worker.start();
}
worker worker=new worker(false); //传个false进去唤醒其余线程
worker.setName("thread-救世主");
worker.start();
}
}
Thread.sleep(1100)在哪个线程使用就睡眠哪个线程。