1.进程与线程
一个程序中至少有一个进程,一个进程中至少会有一个线程。一个进程可以包含多个线程。
看到一个蛮容易理解的比方:
cpu可以看做一个工厂,而进程可以看做是一个车间,每次这个工厂只能有一个车间工作,一旦有一个车间工作其他车间就需要停止工作。也就是说在任何时候,cpu都只运行一个进程。线程就是车间里的工人,一个车间可以有多个工人同时工作,他们可以协调共同完成一个任务。而车间内的房间,工人们是共享的,车间内的工人们都可以进出。这也就意味着一个进程的内存空间,线程是共享的。车间内的房间大小不等,有的房间只能容下一个人,其他人想使用这个房间的话,只有这个小房间内人出去后才可以。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
以上的理解是看到这篇博客进程与线程的一个简单解释写的通俗易懂,配图特别形象。
2.Thread类
Thread 状态:
- 创建(new): 使用new的方式创建一个Thread对象。
- 就绪(runnable) :线程对象调用start()方法,进入就绪状态,此时的线程位于可运行池中,等待cpu调度,获取cpu的使用权
- 运行(running):获取到cpu的使用权,占用cpu,执行run方法中的代码
- 阻塞(blocked):运行状态的线程,遇到阻塞事件,进入阻塞状态,此时虚拟机不会再给线程分配cpu。当阻塞状态解除时,进入就绪状态。
阻塞状态又分几种种情况:1)位于对象等待池中的阻塞状态,也就是调用wait()方法;2)对象锁中的阻塞状态,在使用某个对象的同步锁时,该对象的同步锁已经被其他线程占用;3).使用了sleep()方法或者join()方法
2.1 两种创建线程的方式
创建一个线程一共有三种方式,其中两种常见的是:
- 继承Thread类
- 实现Runnable接口
2.1.1继承Thread类
class MyThread_1 extends Thread {
public MyThread_1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---->" + i);
}
System.out.println(System.currentTimeMillis());
}
}
开启线程就是:
new MyThread_1("thread_1").start()
Thread的构造方法有8种,常见的也就4种:
- Thread()
- Thread(Runnable target)
- Thread(Runnable target, String name)
- Thread(String name)
2.1.2实现Runnable接口
class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println("--->" + num--);
}
}
}
}
开启线程就是:
new Thread(new Ticket()).start()
受限于Java单继承可多实现的特点,一般都常用第二种方式,往往也是直接采用匿名内部类。
2.2 Thread类的常见方法
- setName(String name) getName()
给Thread设置自定义的名字。也可以采用构造方法传值。对应的便是拿到Thread的名字。如果不设置线程的名字,拿到的默认名字是"Thread-0,1,2..."。
- currentThread()
获取当前线程
- start()
开启线程。启动线程后,开始执行run()方法中的代码。
- setPriority(int newPriority) getPriority()
为线程设置优先级。优先级从1到10。默认是5。
- sleep(long millis)
休眠毫秒数
- join()
线程加入或者线程合并,加入的线程执行结束才会执行其他的线程。
- yield()
线程让步,暂停当前正在执行的线程,把执行权给其他线程,执行其他的线程
- interrupt()
中断线程。并不是停止一个线程,给线程加一个中断标记。使线程从阻塞(冻结)状态到运行状态。
- isInterrupted()
判断线程是否中断成功。
上面的几个方法,也都是简单学习。了解并不深入。
2.3线程全问题
上面的比方中说,线程就像车间的工人,而车间内的房间工人们都可以出入的。当多个工人同时工作时,有时需要使用同一个房间内的原材料,如果多名对原材料有操作资格的工人同时都进入这个房间的话,都同时对材料进行操作会存在安全隐患,这时就需要考虑给这个的房间设置一些限制,只有在没人使用原材料的情况下,有操作资格的工人才可以进入。一旦有一名工人在对原材料进行操作,其他工人就要等待,直到房间内对原材料进行操作的工人出来,下一名对原材料有操作资格的工人才可以进入。
也就是说多线程在一些情况下会出一些安全情况。
2.3.1同步锁
当多个线程要同时操作一个共享的数据,共享数据执行过程中还进行了变换,其中一个线程正在对共享数据进行操作变换时,没有执行结束,另外一个线程就参与进来,就会导致共享数据的操作错误。这个安全问题其实就是大名鼎鼎的生产者消费者问题。
解决的思路就是每次只允许一个线程进行共享数据的操作,加入了同步锁。
public class Thread_2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t_1 = new Thread(ticket, "one");
Thread t_2 = new Thread(ticket, "two");
t_1.start();
t_2.start();
}
}
class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
while (true) {
synchronized (Ticket.this) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + num--);
}
}
}
}
}
2.3.2 关于wait,notify,notiryAll
需要注意的是,wait(),notify(),notifyall()
这几个方法并不是Thread类中的,而是Objecet类的。这几个方法是根据需要有时要用在同步锁中,同步锁的对象是可以为任何对象的。在同步代码块中,调用这些方法是需要同步锁对象的,如果这3个方法是在Thread类中,就不符合实际的需要,不满足能够被任意同步锁调用。
贴一个学习时,敲的代码:
public class Thread_3 {
public static void main(String[] args) {
Person person = new Person();
new Thread(new Input(person)).start();
new Thread(new Output(person)).start();
}
}
class Input implements Runnable {
private Person person;
public Input(thread_demo.Person person) {
this.person = person;
}
@Override
public void run() {
if (person != null) {
int x = 0;
while (true) {
if (x == 0) {
person.set("Java", "22");
} else {
person.set("李四", "五");
}
x = (x + 1) % 2;
}
}
}
}
class Output implements Runnable {
private Person person;
public Output(Person person) {
this.person = person;
}
@Override
public void run() {
if (person != null) {
while (true) {
person.out();
}
}
}
}
class Person {
private String name;
private String age;
private boolean flag;
public synchronized void set(String name, String age) {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
flag = true;
this.notifyAll();
}
public synchronized void out() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "--->" + age);
flag = false;
this.notifyAll();
}
}
2.3.3单例模式-懒汉式 防止多线程创建多个对象
public class Singleton {
private static Singleton singleton = null;
private Singleton() {};
public static Singleton getInstance() {
if (singleton == null) {//为了减少对同步锁的判断
synchronized (Singleton.class) {//静态方法中没有this
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
最后
一直以来就感觉多线程是个很难的东西。大三学习Java课程老师上课讲解生产者消费者时,觉得好复杂啊,多线程果然好难。虽然现在工作经验不多,但时隔一年半,再来看这个问题时,也显得不那么复杂了。不过,对于多线程依然觉得很难。目前对于线程的状态还不是很清晰
在我目前的Android开发经验中,对于多线程的使用不多,虽然Android中不允许在UI主线程来进行网络请求,但目前各个网络请求框架都有对异步线程封装,结合Handler便可以实现对UI内容的更新,显示网络请求拿到的数据。目前对Thread的了解比较浅显。
使用的场景也往往比较简单。记得比较清楚的两个场景,一个开启一个线程用来计时,一个用于实现广告页无限轮播。感觉这两个使用场景也可以用Android中的Handler来代替,而且实现过程也相对简单。接下来打算用两种方式再来实现一下在Android中计时。然后找本书,再深入点学习,就自己尝试一下实现多线程下载一个大文件。