多线程学习(一)
进程
进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程。一个进程有自己的方法区和堆。
线程
进程可以进一步细化为线程,是程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。
同一进程里的不同线程使用同一方法区和堆空间。所以一个进程中的多个线程共享相同的内存空间,从同一个堆中分配对象,可以访问相同的变量和对象,这虽然使得线程间通信更加简便高效,但是也同样使得多个线程共享的系统资源受到了安全隐患。
一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,当然如果发生异常,会影响主线程。
并行:多个CPU同时执行多个任务
并发:一个CPU(采用时间片)看似同时执行多个任务
多线程的创建
Thread中的常用方法:
1.start():启动当前线程,调用当前线程的run()
2.run():通常需要重写Thread中的此方法,将创建线程需要执行的操作写在里面
3.currentThread():静态方法,返回当前执行代码的线程
4.getName():设置当前线程的名字
5.getName():获取当前线程的名字
6.yield():释放当前CPU的执行权,但是在多个线程的情况下,释放后得到执行权的可能是该CPU也可能是其他CPU
7.join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,等待b执行完后a才结束阻塞状态,等待CPU分配资源后执行
方式一:继承于Thread类
1.创建一个继承于Thread类的子类
2.重写Thread类的run(),–>将此线程执行的操作声明在run方法中
3.创建Thread类中的子类对象,‘
4.通过此对象调用start()方法
注意:这里调用的是start()方法,开启了另一个线程,如果直接调用run()方法就还是在主线程中。
public class MyThreadDemo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.start();
MyThread2 t2 = new MyThread2();
t2.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++){
if(i%2==1){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
结果如下:可以看出是两个线程在运行
class Window extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
注意:这里的ticket有static
方法二:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
class Window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class WIndowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
比较这两种方式:
优先选择实现Runnable接口的方式,原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合处理多个线程数据共享的情况
线程的生命周期
5个状态:新建 就绪 运行 死亡 阻塞
线程的安全问题
出现的原因:在某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,对同一对象进行操作。
解决想法:当一个线程在操作共享数据时(即使该线程被阻塞),其他线程不能参与进来,等该线程执行结束后,其他线程才能开始对其进行操作。
措施:在java中通过同步机制,来解决线程的安全问题
优缺点:同步方式,解决了线程的安全问题,,但是操作同步代码时,只能有一个线程参与,其他线程等待,相当一个单线程过程,效率较低。
方式一:同步代码块
synchronized(同步监视器:锁(要求多个线程共用一把锁)){
//需要被同步的代码(对共享数据进行操作的代码)
}
说明:任何一个类的对象,都可以充当锁。
class Window2 implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true){
synchronized(obj){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public static class WindowTest2 {
public static void main(String[] args) {
Window2 w2 = new Window2();
Thread t1 = new Thread(w2);
Thread t2 = new Thread(w2);
Thread t3 = new Thread(w2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
}
这里使用的是创建多线程的第二种方法,这里里只创建了一个对象,所以只有一把锁:obj,所以不会出现错票和重票的情况。
下面给出一种错误写法:
package com.company;
class Window3 extends Thread {
private static int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public static class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
}
代码运行结果如下:
分析:出现了重票,这是因为创建了三个对象,有三个obj,共用的不是一把锁,如果想要正确的话,需要将obj设置为static或者synchronized(Window3.class)
方式二:同步方法
说明:1.同步方法任然涉及到同步监视器,只是不需要显式声明
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身