不要自卑,去提升实力
互联网行业谁技术牛谁是爹
如果文章可以带给你能量,那是最好的事!请相信自己
加油o~
多线程
关于多线程有关的概念:
- 进程:进程指正在运行的程序,并且具有一定独立功能
- 线程:线程是进程中的一个执行单位,负责当前进程程序的执行,一个进程中至少会有一个线程,如果一个进程中包含多个线程,那么可称为多线程程序
- 单线程:当要执行多个任务时,cpu只会依次执行,当一个任务执行完后,再去执行另外一个任务
- 多线程:多个任务可以同时进行
在Java中,不同线程会有不同的优先级抢占cpu,如果线程优先级相同,就会随机先去一个线程去执行
Java程序运行时会默认执行3个进程:
- main主线程
- gc垃圾回收机制
- 异常处理机制
我们如何能够判断程序是否是多线程?
如果我们能够将程序的执行用一条直线画出来,就说明是单线程
关于线程的常用API方法
-
run():该方法需要被重写,重写的内容就是需要执行的操作
-
start():调用该方法就会启动相应的线程,并调用当前线程的run方法
-
sleep(long millitime):将当前线程进入阻塞状态(不会释放锁,即同步监视器)
-
join():当a线程调用b线程的join方法时,a线程会进入阻塞状态,直到b线程的任务执行完毕
-
isAlive():判断当前线程是否存活
-
yield():调用该方法后回释放当前线程的cpu执行权,当时并不代表不会再次执行,有可能释放后,又是该线程抢占到了cpu的执行权
-
currentThread():Thread类中的静态方法,会返回执行当前程序的线程
-
getName():返回当前线程的名字
-
setName():设置线程的名字
-
getPriority():设置线程的优先级(
MAX_PRIORITY=10
MIN_PRIORITY=1
NORM_PRIORITY=5 默认优先级
-
wait():将线程进入阻塞状态(会释放掉锁),只能在同步代码块或同步方法中使用
-
notify():将另外一个线程唤醒
-
notifyAll():唤醒所有被阻塞的线程
线程创建的4种方式
一:继承Thread类
- 首先创建一个类去继承Thread
- 重写Thread中的run方法
- main()中创建该对象
- 调用该对象的start方法,启动线程
public class test {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
myThread1.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("继承了Thread");
}
}
二:实现Runnable接口
- 创建一个类实现Runnable接口
- 实现接口的run方法
- 在main()中创建实现Runnable的对象
- 创建Thread对象,并把刚创建好的类传参
- 调用Thread对象的start方法,启动线程
public class test {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
Thread t=new Thread(myThread1);
t.start();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("实现了Runnable");
}
}
三:实现Callable接口
- 创建一个类实现Callable接口
- 实现接口的call()方法
- 在main中创建实现Callable的对象
- 创建FutureTask对象并把上面创建的对象传参
- 创建Thread对象,传入FutureTask对象
- 调用Thread的start方法,启动线程
public class test {
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
FutureTask futureTask=new FutureTask(myThread2);
Thread t=new Thread(futureTask);
t.start();
}
}
class MyThread2 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("实现了Callable");
return null;
}
}
此方式如果需要得到返回值需要调用futureTask.get();
但是会抛异常,用try,catch方法捕捉一下就好了
四:线程池
- 创建ExecutorService对象
- 传入相应的线程对象
- 结束线程池
public class test {
public static void main(String[] args) {
//创建线程池,设置线程池线程的数量为10
ExecutorService service = Executors.newFixedThreadPool(10);
//execute适用于实现了Runnable的对象
service.execute(new MyThread1());
//submit适用于实现了Callable的对象
service.submit(new MyThread2());
//结束线程池
service.shutdown();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("实现了Runnable");
}
}
class MyThread2 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("实现了Callable");
return null;
}
}
Runnable和Callable的对比:
- Callable可以有返回值,执行完相应操作后可以返回需要的结果
- 可以抛异常,可以将call方法中的异常抛出
- 支持泛型,可以指定返回值类型
线程安全
什么是线程安全问题呢?
当多个线程对同一个共享数据操作时,线程执行还没来得及更新处理共享的数据,从而使得其他操作的线程并未得到最新的数据,从而产生问题
举个例子:
- 当甲乙两人向同一账户存钱,让甲乙两个线程同时存钱,如果甲向账户存了1000元,并打印此时余额,应为1000元,但是如果此时乙也存了1000元,就会导致,显示余额为2000元,并不是甲当时的余额
- 还有就是火车售票问题,如果多个窗口同时售票,如果1号窗口正在卖001号票时,此时还未处理完成,这是2号窗口也卖了001号票,这就导致产生了两个001号票
那么如何解决呢?
有三种方式:
方法一:同步代码块
synchronized(Object obj)
{
//操作内容
}
- synchronized():传入的可以是任意类的对象,但必须是多个线程共用的,一般可以利用this,即当前对象(Runnable),Thread不太行,因为继承多个Thread类会导致this对象不一致
- 被包住的代码执行为单线程,当一个线程执行完后,另外一个线程才有可能会分配到执行权去执行
- 多个线程必须共用同一把锁,这样才能够判断一个线程是够执行
- Runnable一般很实用,因为多个线程都调用同一个类的方法,但是Thread就需要自己定义静态变量或者当前的唯一类即(windows.class)
public class test {
public static void main(String[] args) {
Window t1 = new Window("窗口1");
Window t2 = new Window("窗口2");
Window t3 = new Window("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread{
private static int ticket=100;
//继承方式,要用静态对象,因为继承实现多线程有多个对象,不是共用一个对象
private static Object obj=new Object();
public Window(String name){
super(name);
}
@Override
public void run() {
while(true){
//不可以用this,同理,因为有很多对象,不唯一
synchronized(obj){
if(ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
方法二:同步方法
和同步代码块类似
就是将共享数据的操作封装成方法
将这个方法用锁锁住
public class test {
public static void main(String[] args) {
Window t1 = new Window("窗口1");
Window t2 = new Window("窗口2");
Window t3 = new Window("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread{
private static int ticket=100;
public Window(String name){
super(name);
}
@Override
public void run() {
while(true){
show();
}
}
public static synchronized void show(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
注意:
- show方法要定义成静态,因为如果是非静态,它的锁默认是当前对象,继承方式就会有多个锁,如果是Runnable就可以,所以需要编程静态,这样默认锁就是当前类对象
方法三:lock锁
- 首先创建一个ReentrantLock对象
- 在执行共享数据之前将锁打开,调用lock方法
- 在结束时将锁解开,调用unlock方法
public class test {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable{
private int ticket=100;
private ReentrantLock lock=new ReentrantLock(true);
@Override
public void run() {
while(true){
try {
lock.lock();
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
else{
break;
}
} finally {
lock.unlock();
}
}
}
}
问题:该方式和synchrnized有什么不同呢?
synchronized在执行完相应代码后会自动上锁解锁,而lock需要手动上锁和解锁,较为灵活
线程的死锁
描述:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
举个例子:两个人迎面相遇,甲希望乙会给他让路,而乙希望甲给他让他让路,就这样两个人僵持在这里,最终谁也不给谁让路,导致死锁问题
public class test {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
本例中,线程1首先拿到了s1锁,然后阻塞了一段时间,这段时间线程2拿到了s2锁,这是线程1就绪需要s2锁,而线程2需要s1锁,两者谁都拿不到,就会僵持住
解决死锁的相应办法:
- 减少共享变量的使用
- 设计相应的算法去规避死锁问题
- 尽量减少锁的嵌套使用
线程的通信
-
wait():将线程进入阻塞状态(会释放掉锁),只能在同步代码块或同步方法中使用
-
notify():将另外一个优先级高的线程唤醒
-
notifyAll():唤醒所有被阻塞的线程
注意:这三个方法只能够在同步代码块或者同步方法使用,都定义在了Object类中
例题要求:
让两个线程交替打印1-100之间的数字
public class 线程通信 {
public static void main(String[] args) {
Number number=new Number();
Thread t1=new Thread(number);
Thread t2=new Thread(number);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
//唤醒全部
// notifyAll();
if(number<=100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
//使得调用如下方法进程阻塞,执行wait后,锁就被释放
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
- 当线程1首次进入,会打印出1,然后调用了wait方法 ,进入阻塞状态
- 此时线程2进入,首先会唤醒线程1,然后打印2,然后自己进入阻塞状态
- 两者交替阻塞唤醒,直到打印完为止
那么问题是sleep和wait方法有什么异同?
两个方法声明的位置不同,sleep是Thread中声明的,而wait是Object中声明的
sleep可以在任何情景调用,而wait只能够在同步代码块或同步方法中使用
sleep执行后不会释放当前的锁,而wait会释放掉当前的锁
生产者和消费者问题:
生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
package 生产者与消费者问题;
public class 生产者 {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者");
p1.start();
c1.start();
}
}
class Clerk{
private int productCount=0;
public synchronized void consumeProduct() {
if(productCount>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void produceProduct() {
if(productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName()+":开始生产产品......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(getName()+":开始消费产品......");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
- 生产者和消费者会共享产品数量
- 我们可以在售货员类中定义方法,当此时的生产数量未达到标准时,就会进行生产,然后会唤醒消费的进程,否则就会进入阻塞状态
- 而当产品数量不足时,消费者就会唤醒生产进程,此时,自己进入阻塞状态