1.程序,进程,线程
程序:为了完成特定任务,用某种语言编写的一组指令的集合。即一段静态的代码
进程:也可以称为程序,但是是再内存中运行的程序。进程是操作系统进行资源分配的最小单位
线程:进程可进一步细化为线程,是一个进程内部中的最小执行单位,是CPU进行调度的最小单元
进程和线程的关系:
1.一个进程可以包含多个线程,例如:一个qq可以有多个聊天窗口
2.一个线程只能隶属于一个进程,例如微信的聊天窗口只属于微信进程
3.一个进程至少包含一个线程(主线程 java中的main方法是用来启动主线程的)。在主线程中可以创建并启动其他线程
4.一个进程中的所有线程共享该进程的所有资源
创建线程
方法1:
写一个类继承Thread
重写run()方法
线程中要执行的任务要写在run()中,或者在run()中进行调用
package Day1;
public class ThreadTest extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("run"+i);
}
}
public static void main(String[] args) {
//创建并启动线程
ThreadTest threadTest = new ThreadTest();
threadTest.start();
for (int j = 0; j < 1000; j++) {
System.out.println("main"+j);
}
}
}
方法2:
只先创建线程要执行的任务,创建一个类,实现Runnable接口
package Day1;
public class Task implements Runnable {
//重写run()
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("run"+i);
}
}
public static void main(String[] args) {
//创建任务
Task task=new Task();
//创建线程并接收任务
Thread thread=new Thread(task);
thread.start();
for (int j = 0; j < 1000; j++) {
System.out.println("main"+j);
}
}
}
实现Runnable接口的优点:
1.java是单继承,一旦继承一个类,就不能在继承其他类,避免了单继承的局限性
2.适合多线程来处理同一份资源时使用
方法3:
Callable接口
重写Call方法
package Day2.CallableThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Call<T> implements Callable<T> {
@Override
public T call() throws Exception {
Integer n=0;
for(int i=0;i<10;i++){
n+=i;
}
return (T)n;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Call<Integer> call=new Call();
FutureTask<Integer> futureTask=new FutureTask(call);
Thread thread=new Thread(futureTask);
thread.start();
Integer num=futureTask.get();
System.out.println(num);
}
}
Thread类中的方法
run();用来定义线程要执行的任务代码
start(); 用来启动线程
currentThread(); 获取当前线程
getId(); 获取线程Id
getName(); 获取线程名字
getPriority(); 获取线程优先级
getstate(); 获取线程状态
package Day1;
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(MyThread.currentThread().getName()+":"+MyThread.currentThread().getPriority());
}
public static void main(String[] args) {
MyThread myThread=new MyThread();
System.out.println(MyThread.currentThread().getState());
myThread.setName("窗口1");
myThread.setPriority(8);
myThread.start();
MyThread myThread1=new MyThread();
System.out.println(MyThread.currentThread().getState());
myThread1.setName("窗口2");
myThread1.setPriority(9);
myThread1.start();
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
}
}
sleep(); 让线程阻塞休眠指定的时间
join(); 等待当前线程完毕,其他线程再执行
yield(); 主动礼让,退出CPU
package Day1;
public class MyThread1 extends Thread{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<1000;i++){
if (i%10==0){
Thread.yield();
}
System.out.println(MyThread1.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread2=new MyThread1();
myThread2.setName("窗口3");
myThread2.start();
//myThread2.join();
MyThread1 myThread3=new MyThread1();
myThread3.setName("窗口4");
myThread3.start();
}
}
线程生命周期
线程状态
新建:刚刚创建一个线程对象,并没有启动
就绪(可运行):调用start()后,线程就
进入到了就绪状态,进入到了操作系统的调度队列
运行状态:获得了CPU执行权,进入到CPU执行
阻塞状态:例如调用了sleep(),有线程调用了join(),线程中进行Scanner输入
死亡/销毁: run()方法中的任务执行完毕了
多线程
在一个程序中可以创建多个线程
优点:
提高了CPU的利用率
提高程序执行效率
改善了程序结构
缺点:
线程也占空间,线程越多,占的内存空间就越多
多线程需要协调和管理,所以需要跟踪管理线程,使得CPU开销变大
线程之间同时对共享资源的访问会互相影响,如果不加以控制会导致数据出错
如何解决多线程操作共享数据的问题?
排队+锁:在关键的步骤,多线程只能一个一个的执行
synchronized(//同步锁对象){
//同步代码块
}
同步锁作用:用来记录有没有线程进入到同步代码块,如果有线程进入到同步代码块,那么其他线程就不能进入同步代码块,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入
同步锁要求:可以是任意类的对象
同步锁必须是唯一的(多个线程拿到的是同一个对象)
package Day1;
public class Thread1 extends Thread {
static int num=10;
static String obj=new String();
@Override
public void run() {
while(true) {
synchronized (obj) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "成功抢到第" + num + "张票");
num--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread1 thread1=new Thread1();
thread1.setName("窗口1");
thread1.start();
Thread1 thread2=new Thread1();
thread2.setName("窗口2");
thread2.start();
}
}
obj对象的作用:记录有没有线程进入到同步代码块
要求:多个线程对应同一个对象
synchronized修饰方法时,同步锁对象不需我们指定
同步锁对象会默认提供:
1.非静态的方法--锁对象默认是this
2.静态方法--锁对象是当前类的Class对象(类的对象,一个类的对象只有一个)
synchronized同步方法
package Day1;
public class Thread1 extends Thread {
static int num=10;
@Override
public void run() {
while(true) {
print();
if(num<=0) {
break;
}
}
}
public static synchronized void print() {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "成功抢到第" + num + "张票");
num--;
}
}
public static void main(String[] args) {
Thread1 thread1=new Thread1();
thread1.setName("窗口1");
thread1.start();
Thread1 thread2=new Thread1();
thread2.setName("窗口2");
thread2.start();
}
}
ReentrantLock和 synchronized的区别:
synchronized是一个关键字,控制依靠底层编译后的指令去实现。也可以修饰一个方法或者一个代码块。synchronized是隐式的加锁和释放锁,一旦方法出现异常或者运行结束,会自动释放锁
ReentrantLock是一个类,依靠java中的代码去控制(底层有一个同步队列)。它只能修饰代码块。
ReentrantLock需要手动释放锁,所以最好放在finally中,当代码出现异常,也会释放锁
线程安全问题:
多线程情况下,访问一个共享数据(临界资源)
线程通信
线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用
package Day2;
public class NumThread extends Thread{
static int num=0;
static String obj=new String();
@Override
public void run() {
synchronized (obj) {
while (num <= 100) {
obj.notify();//唤醒一个被wait的线程
System.out.println(Thread.currentThread().getName() + ":" + "我是傻鸟"+num+"号");
num++;
try {
obj.wait();//阻塞一个线程,并释放同步锁对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
NumThread numThread1=new NumThread();
NumThread numThread2=new NumThread();
numThread1.setName("磊");
numThread2.setName("亮");
numThread1.start();
numThread2.start();
}
}
sleep和wait的区别:
sleep(long time)
属于Thread类中的方法
sleep休眠一段时间,会自动唤醒
sleep不会释放锁
wait()
属于object类中的方法
wait后的线程,必须通过其他线程用notify()方法或者notifyAll()方法去唤醒
阻塞一个线程,并释放同步锁对象