进程:指一个内存中运行的应用程序。例如运行QQ那么它就是一个进程,而且一个应用程序可以同时运行多个进程
线程:线程是进程中的一个执行单元,就比如用360我们可以让它一边杀毒一边清理垃圾,那么360它就是一个进程,那么杀毒和清理垃圾就是进程下的两个线程
注:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName() :获取当前线程名称。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
创建线程的方式总共有两种:
1.是继承Thread类方式
2.是实现Runnable接口
创建线程的方式一:
代码如下:
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
t1.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("第一种方式创建线程");
}
}
创建线程的方式二:
代码如下:
public class Test {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
t1.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("第二种方式创建线程");
}
}
因为类都是单继承的,如果一个类继承Thread,就不可以继承其他类了。但是如果实现了Runnable接口的话,则很容易的实现资源共享,避免了java中的单继承的局限性,所以Runnable比Thread更有优势
使用匿名内部类的方式实现Runnable接口,重写Runnable接口中的run方法:
代码如下:
public class Test {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("匿名内部类创建线程");
}
}).start();
}
}
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
那么什么是线程不安全的呢?我们由一段代码看一下:
这里我想打印的是1-100之间的整数:
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
private int sum = 100;
public void run(){
while(true){
if(sum>0){
try {
Thread.sleep(10);
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
}
运行结果出现了很多重复的,甚至还有-1,结果和预期是不一样的,这就是线程不安全情况。那么为什么会出现这种情况呢?是因为这三个线程在执行过程中不断抢夺CPU的执行权,当某一个线程运行到Thread.sleep(10)的时候处于睡眠状态,那么CPU的执行权交给了另外两个线程以此类推,三个线程都执行到了这里,这时代码就不是一条判断一条输出了,当睡眠结束后三条线程面临的都是一条输出语句一个sum–不再判断sum的值,若最后判断sum的值为1,最后sum的值将被–三次,所以才会导致最终的结果出现0和-1的情况(最终sum的值为-2),这里的Thread.sleep()其实是为了增加线程安全问题出现的概率。
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制(synchronized)来解决。
无论是否失去CPU的执行权,让其他线程只能处于等待状态
格式:
synchronized(同步锁){
//需要同步操作的代码
}
使用同步代码块解决代码(运行结果准确无误的输出1-100之间的整数):
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
private int sum = 100;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if (sum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
}
}
修饰的方法,就叫做同步方法,保证某一线程执行该方法的时候,其他线程只能在方法外等着。
使用同步方法代码如下:
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable {
private int sum = 100;
Object obj = new Object();
public void run() {
while (true) {
show();
}
}
public synchronized void show() {
if (sum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
锁定操作比synchronized代码块和synchronized方法更广泛, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用如下:
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable {
private int sum = 100;
Lock l = new ReentrantLock();
public void run() {
while (true) {
l.lock();
if (sum > 0) {
try {
Thread.sleep(10);
System.out.println(sum);
sum--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
}
为一个线程增加了限时等待的状态。在我们写1-100之间整数的案例中,为了增加线程安全问题出现的概率也为了减缓执行速度,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行)。 其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,到时间自动苏醒,其实就是所谓的计时等待状态。
一个线程正在等待一个监视器锁(锁对象)的线程处于的状态,也就是为获取锁对象的状态比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,那么线程B就进入到锁阻塞状态。
一个线程处于正在无限期等待另一个线程执行一个特别的(唤醒)动作的这一状态,也就是调用wait()方法进入无限等待状态,直到调用notify方法,释放锁对象。
下面通过一段代码理解一下:
public class ThreadDemo {
private static boolean isfinished;
private static Object obj = new Object();
public static void main(String[] args) {
new Thread() {
public void run() {
System.out.println("开始下载图片");
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("down:图片已下载:" + i + "%");
}
System.out.println("图片下载完毕");
isfinished = true;
synchronized (obj) {
obj.notify();
}
System.out.println("开始下载附件");
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("附件:图片已下载:" + i + "%");
}
System.out.println("附件下载完毕");
}
}.start();
new Thread() {
public void run() {
if (!isfinished) {
System.out.println("图片还没下载完成");
}
System.out.println("开始显示图片");
try {
synchronized (obj) {
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("图片显示完毕");
}
}.start();
}
}
通过上述案例我们会发现,一个调用了某个对象的wait 方法的线程会等待另一个线程调用此对象的 notify()方法。其实无限等待状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。当多个线程协作时,比如A,B线程,如果A线程在可运行状态中调用了wait()方法那么A线程就进入了无限等待状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入可运行状态;如果没有获取锁对象,那么就进入到锁阻塞状态。
这是多个线程间的一种协作机制。就是在一个线程进行了规定操作后,就进入等待状态(wait()),等待其他线程执行完他们的指定代码过后再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
调用wait和notify方法需要注意的细节
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果。
其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。利用线程池能够带来的好处:
线程池接口是 ExecutorService 。Executors类中有个创建线程池的方法如下:public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。获取到了一个线程池ExecutorService 对象,然后定义一个使用线程池对象的方法如下:public Future> submit(Runnable task) :获取线程池中的某一个线程对象
使用线程池中线程对象的步骤:
实现代码:
public class Test {
public static void main(String[] args) {
// 1、生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 2、调用submit方法,传递线程任务(实现类),开启线程执行run方法
es.submit(new RunnableImpl());// Thread[pool-1-thread-2,5,main]执行
// 线程池会一直开启, 使用完了线程,会自动把线程会还给线程池,线程可以继续使用
es.submit(new RunnableImpl());// Thread[pool-1-thread-1,5,main]执行
es.submit(new RunnableImpl());// Thread[pool-1-thread-2,5,main]执行
// 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
//es.submit(new RunnableImpl());// 抛异常,线程池都没有了,就不能获取线程了
}
}
// 创建一个类,实现Runnable接口,重写run方法,设置线程任务
class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread()+ "执行");
}
}