首先,什么是多线程。我的理解是多线程就是多个线程同时进行。比如说买火车票,好几个人同时购买火车票,每一个人购买火车票都是一个线程,多个人同时购买自然就是多线程了。
说到线程,有一个与之相关的概念叫做进程。所谓进程,就是对于程序的一次执行的过程。还是以买火车票为例,买火车票就是进程,因为他要执行买火车票的这个程序。然后呢,一个进程可以有多个线程,但至少得有一个线程,不然就没有意义了。你可以多个人同时买火车票,但起码要有一个人买,不然没人买票跑程序干嘛呢?
对于线程,一般来说有三种创建的方法。第一是继承Thread类,第二种是实现Runnable接口,最后一种是实现Callable接口。
首先是继承Thread类,这个有三步。
第一步是自定义线程继承Thread类
第二步是重写run()方法
第三步是创建线程对象,调用start()方法启动线程
以我写的这个为例
package Demo02;
public class ThreadExample extends Thread{
@Override
public void run() {
Print print=new Print();
for (int i = 0; i < 3; i++) {
print.print(i);
}
}
public static void main(String[] args) {
ThreadExample t1=new ThreadExample();
ThreadExample t2=new ThreadExample();
ThreadExample t3=new ThreadExample();
t1.start();
t2.start();
t3.start();
}
}
class Print{
public void print(int i){
System.out.println(i);
}
}
运行以后的结果如图
然后是实现Runnable接口
同样是分为三步
第一步是自定义一个类实现Runnable接口
第二步是实现run()方法
第三步是创建线程对象,调用start()方法启动线程
以此为例:
package Demo02;
public class RunnableExample implements Runnable{
private String name;
public RunnableExample(String name){
this.name=name;
}
@Override
public void run() {
Print1 print=new Print1();
print.print(name);
}
public static void main(String[] args) {
RunnableExample t1=new RunnableExample("小红");
RunnableExample t2=new RunnableExample("小蓝");
RunnableExample t3=new RunnableExample("小黄");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
class Print1{
public void print(String name){
System.out.println(name);
}
}
总的来说,我觉得实现Runnable接口是比继承Thread类要好的,其中有一个很关键的点就是实现Runnable接口可以避免单继承局限性,可以同一个对象被多个线程使用,如下例子:
package Demo02;
public class TestThread implements Runnable{
private int num=10;
@Override
public void run() {
while (true){
if (num<=0){
break;
}else{
System.out.println(Thread.currentThread().getName()+"获得第"+num--+"张票");
}
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
new Thread(testThread,"小红").start();
new Thread(testThread,"小橙").start();
new Thread(testThread,"小黄").start();
new Thread(testThread,"小绿").start();
new Thread(testThread,"小青").start();
new Thread(testThread,"小蓝").start();
new Thread(testThread,"小紫").start();
}
}
但是在线程并发上他会出问题,所以后面会有一个线程同步的解决方法
有一个非常经典的多线程的例子,就是龟兔赛跑,为了符合故事内容,我设置了兔子在跑到50米的时候会睡上2秒
package Demo02;
public class Race implements Runnable{
private String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i==50 && Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
boolean flag = result(i);
if(flag){
break;
}
}
}
private boolean result(int i){
if (winner!=null){
return true;
}
if(i==100){
winner = Thread.currentThread().getName();
System.out.println(Thread.currentThread().getName()+"获胜");
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
这个案例就可以很清除的展示了多线程的概念。
然后最后一种方法是实现Callable接口
这种方法的步骤就比较多了
首先是创建一个类,实现Callable接口
然后重写call方法
然后创建目标对象
然后创建执行服务
然后提交执行
最后获取结果,关闭服务
说完了三种方法,接下来就可以说一下静态代理了。
所谓静态代理其实就是创建一个代理对象,但是这个代理对象可以做很多真实对象做不了的事,让真实对象专注去做自己的事。不过要求他们要实现同一个接口。
以租房子为例,我要租房子,那我需要做的就是给钱,签合同,那么之前的挑房子以及签完合同以后交付钥匙就交给中介,也就是第三方的中间商就可以了。在这个例子中,需要租房子的我,就是真实对象,而中间商就是代理对象。我们两都实现了一个租房的接口。
package Demo02;
public class StaticProxy {
public static void main(String[] args) {
//我要租房子
Mine mine = new Mine();
Business business=new Business(mine);
business.HireHouse();
}
}
//被实现的同一个接口
interface Hire{
void HireHouse();
}
//真实角色,需要租房子的我
class Mine implements Hire{
@Override
public void HireHouse() {
System.out.println("付钱,签合同");
}
}
//代理角色,租房子的中间商
class Business implements Hire{
private Hire hire;
public Business(Hire hire) {
this.hire = hire;
}
@Override
public void HireHouse() {
before();
this.hire.HireHouse();
after();
}
private void after() {
System.out.println("交付钥匙");
}
private void before() {
System.out.println("挑房子");
}
}
运行起来就是这样的
然后说到线程的状态,众所周知,线程有五种状态。分别是创建状态,就绪状态,运行状态,阻塞状态和死亡状态。
首先说创建状态,顾名思义,就是线程刚被创建出来,也就是刚被new出来。这个时候我们调用start()方法就一个让线程进入到就绪状态。这个时候线程其实是准备就绪了,但是他要等待CPU调度,如果说获得了CPU的资源,那么线程就从就绪状态变成了运行状态,这个时候我们的线程就算正式跑起来了。进入到运行状态的线程会有几种结果,第一种就是线程自然执行完成或者因为外部的干扰导致线程终止了,这个时候线程就变成了死亡状态。还有一种情况是因为一些原因导致线程需要暂时停止,比如sleep、wait、join、yield等,这个时候线程就会变成阻塞状态,等到线程会唤醒就会变成就绪状态,等待CPU调度。
前面提到了从运行状态变成阻塞状态会有好几种情况,接下来就来说一下这几种情况。
首先是yield,这是线程礼让。其实很好理解,就是说有两个线程,A线程已经是运行状态了,这个时候B线程自然就是就绪状态了。然后线程礼让就是让A线程再变成就绪状态,两个线程公平竞争,让CPU重新调度,看最后哪个线程会变成运行状态。
然后是sleep,这个其实很常见了,就是线程休眠,他主要是可以放大问题的发生性,有的时候因为线程跑的太快了,所以很多问题我们发现不了,这个时候让他们去休眠一段时间,很多问题就可以被发现了。很多时候,sleep用来模拟网络延时和倒计时。
然后是join,join可以理解为插队,就是说有一个线程,他想要先执行,那么他就让别的线程先阻塞,等他执行完以后再执行别的线程。
还有一个wait(),他也是等待,但是他和sleep不一样的是,sleep不会释放锁,而wait会释放锁,而且wait可以用notify()方法来唤醒。
如果我们想要看自己的线程的状态,那么就可以用state=thread.getState();
来查看。
而且因为我们都知道线程谁先执行谁后执行是按照优先级来的,我们如果想要改变或者获取优先级的话就可以用getPriority()和setPriority()方法。
然后设计到一个守护线程的概念。线程一般是分为用户线程和守护线程的,虚拟机必须要确保用户线程执行完毕,但是不会管守护线程的死活。所以说我们比较常见的垃圾回收,后台记录操作日志什么的都是守护线程。如果我们想要把一个自己写的线程设置为守护线程,那么就可以通过thread.setDaemon(true)
的方式进行设置,因为一般默认是false,也就是用户线程。
之前提到了线程同步。所谓线程同步其实就是一种等待机制。举个例子,大家都想上厕所,但是就一个厕所,怎么办呢,那只能按先来后到排队了,这就是队列,厕所一次只能容纳一个人,所以第一个人进去以后就会把门给锁上,不让第二个人进来,这就是锁。所以说线程同步其实就是队列加锁。
线程同步可以用两个方法。
第一个是synchronized关键字,它可以用在方法和代码块里。但是加锁有时候会遇到一个问题,就是死锁。死锁就是A在等B先放手,B在等A先放手,恶性循环,僵住了。
还有一种呢是Lock,但是它和synchronized关键字相比,只有代码块锁,没有方法锁。而且Lock是显性锁,也就是说他是需要我们手动打开和关闭的,不像synchronized是隐性锁,出了作用域就自动释放了。但是Lock的性能要更好。因此一般推荐使用Lock锁。
关于多线程还有两中方法,一种是使用一个缓冲区的管程法,一种是信号灯法。
管程法的代码如下
package Demo02;
public class TestThreadDemo {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Productor(buffer).start();
new Consumer(buffer).start();
}
}
//生产者
class Productor extends Thread{
Buffer buffer;
public Productor(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了第"+i+"个商品");
buffer.push(new Product(i));
}
}
}
//消费者
class Consumer extends Thread{
Buffer buffer;
public Consumer(Buffer buffer){
this.buffer=buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+buffer.pull().id+"个商品");
}
}
}
//产品
class Product{
//产品编号
int id;
public Product(int id) {
this.id = id;
}
}
//缓冲区
class Buffer{
///设置一下容器大小
Product[] products = new Product[10];
//缓冲区内容器商品数量
int count=0;
//生产者放入商品
public synchronized void push(Product product){
//如果容器满了,就等消费者消费
if(count==products.length){
//消费者进行消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果容器没满,那么继续往缓冲区的容器中存入商品
products[count]=product;
count++;
//通知消费者进行消费
this.notifyAll();
}
//消费者取出商品
public synchronized Product pull(){
if(count==0){
//消费者等待,生产者进行生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Product product=products[count];
//通知生产者进行生产
this.notifyAll();
return product;
}
}
信号灯的代码如下
package Demo02;
public class TestThreadDemo2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%5==0){
this.tv.play("广告");
}else{
this.tv.play("明星大侦探");
}
}
}
}
//观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.tv.watch();
}
}
}
class TV{
//节目
String programe;
//标志位
boolean flag=true;
//表演
public synchronized void play(String programe){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演"+programe+"节目");
this.programe=programe;
flag=!flag;
this.notifyAll();
}
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看"+programe+"节目");
flag=!flag;
this.notifyAll();
}
}
最后是线程池,线程经常用的话,老是被创建和销毁,太影响性能了。所以我们可以把创建一个线程池,提前创建线程放在里面,用的时候从里面拿就可以了。
示例代码如下:
package Demo02;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//创建线程池
//参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//创建服务
service.execute(new TestPool());
service.execute(new TestPool());
service.execute(new TestPool());
service.execute(new TestPool());
//关闭连接
service.shutdown();
}
}
class TestPool implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"第"+i+"个");
}
}
}
运行结果如下
转载自原文链接, 如需删除请联系管理员。
原文链接:对多线程的理解,转载请注明来源!