2.1-基础知识
程序
所谓的程序其实就是一坨存在于硬盘之上的按照一定格式组织的指令数据,比如:window【exe】,Android【odex】;在没有被调入内存执行前【运行前】,它就是一坨指令数据和其它任何数据【文档,音频,视频等】没有任何本质上的区别;
进程
只有当程序【exe,odex】被调入内存中去运行时,我们才称之为这是一个进程,所以进程描述的是一个被调入内存中去执行的程序【运行的程序-》进程】
进程是独立运行和资源分配的基本单位【进程之间相互独立】
线程
如果说我们把进程比作是一个车间,那么线程就是车间的工人【用来干活的,执行代码指令的,本质:执行函数】,当创建一个进程时,就会默认创建一个线程,这个线程称之为主线程!
线程是调度的基本单位,线程的本质机理:其实是一种保障代码块独立运行的机制【上下文环境,切换等】,去执行一个函数!
2.2-线程状态
----------------------------------------------
Java层:
2.3-创建线程
- 继承:Thread
class MyThread extends Thread {
private int i = 0;
//重写:run
@Override
public void run()
{
for (i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
/* MyThread myThread = new MyThread();
myThread.start();*/
- 实现:Runnable
//实现:Runnable
class MyRunnableThread implements Runnable{
private int i = 0;
//实现:run
@Override
public void run()
{
for (i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
/* MyRunnableThread myRunnableThread = new MyRunnableThread();
Thread myThread = new Thread(myRunnableThread);
myThread.start();*/
- 使用:FutureTask
要素:Callable,FutureTask,Thread
特点:线程执行具备返回值
//实现:Callable泛型接口 备注:Integer返回值类型
class MyCallable implements Callable<Integer>{
//具备返回值
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
Thread.sleep(500);
}
return sum;
}
}
Callable<Integer> myCallable = new MyCallable();
FutureTask<Integer> myFt = new FutureTask<>(myCallable);
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 5){
Thread thread = new Thread(myFt);
thread.start();
}
Thread.sleep(100);
}
try{
//当主线程上面代码执行完后,如果子线程未结束,还未返回结果
//此时:get()方法就会一直等待子线程结束返回结果后,在继续往下执行
int sum = myFt.get();//获取结果
System.out.println("sum: " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
2.4-线程安全
多线程问题:
我们假设房间里有5个人,如果大家各自用各自的洗漱用品,大家不会产生冲突,但是如果存在多个人共用比如,:牙膏;一个人拧开,突然有事走了,然后另外一个人他也用牙膏,他进去直接用了,而且把牙膏给拧上了,等第一个人回来,没法直接挤出来牙膏了,必须在打开一次;
当然这是人的角度,站在代码的角度来看,当多个线程存在时,由于线程切换问题以及资源或代码共用问题,导致访问同一资源,那么就会产生错乱或者异常情况的发生,此时就需要进行线程同步!
package main.java.demo;
public class MySynchMainClass {
static int count = 0;
public static void getCount()
{
System.out.println(Thread.currentThread().getName() + "修改前:" + count);
count ++;
System.out.println(Thread.currentThread().getName() + "修改后:" + count);
}
public static void main(String[] args){
for(int i=0;i<10;i++){
MySynchMainClass.getCount();
if(i == 3 || i == 5 || i == 7){
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
MySynchMainClass.getCount();
}
}
}).start();
}
}
}
}
线程同步
所谓的同步其实指的就是有顺序的去访问和执行同一资源和代码,存在依赖顺序,类似接力赛,一个跑完,另外一个才可以跑!
线程异步
所谓的异步其实指的不访问同一资源和代码,无依赖顺序,各跑各的,类似个人赛,比赛一开始各自跑自的!
同步机制:
- synchronized
package main.java.demo;
public class MySynchMainClass {
int count = 0;
// /*第一种:*/
// public synchronized void getCount()
// {
// System.out.println(Thread.currentThread().getName() + "修改前:" + count);
// count ++;
// System.out.println(Thread.currentThread().getName() + "修改后:" + count);
// System.out.println();
// }
/*第二种:*/
public void getCount()
{
synchronized(this){
System.out.println(Thread.currentThread().getName() + "修改前:" + count);
count ++;
System.out.println(Thread.currentThread().getName() + "修改后:" + count);
System.out.println();
}
}
public static void main(String[] args){
MySynchMainClass mySynchMainClass = new MySynchMainClass();
for(int i=0;i<10;i++){
mySynchMainClass.getCount();
if(i == 3 || i == 5 || i == 7){
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
mySynchMainClass.getCount();
}
}
}).start();
}
}
}
}
2.5-线程通信
----------------------------------------------
Ndk层:
创建线程
void* threadCallbackFunc1(void* args){
LOGD("threadCallbackFunc1 run!")
//void* arg 创建线程时:传递过来的参数
//子线程:功能代码
LOGD("threadCallbackFunc1 end!")
//返回值
return nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1thread_MainActivity_nativeCreateThread(
JNIEnv *env,
jobject thiz) {
/*
* int pthread_create(
* pthread_t* __pthread_ptr, 参数1:pthread_t->long->线程id
* pthread_attr_t const* __attr, 参数2:线程参数集 -> nullptr
* void* (*__start_routine)(void*), 参数3:线程处理函数 -> CPU调度时 -> 执行这个线程处理函数
* void*); 参数4:线程传递参数 -> 线程处理函数 -> 参数
* */
//pthread 它是linux系统的线程API,Android底层也是Linux所以可以直接用
//Windows:CreateThread C++11:std::thread
pthread_t pId;
int ret = pthread_create(&pId, nullptr,threadCallbackFunc1, nullptr);
if(ret != 0){
LOGD("pthread_create err!");
return;
}
/* //C++11 thread
std::thread td(threadCallbackFunc2);
td.join();//阻塞执行
td.detach();//分离执行*/
}
探究元素
探究JNI函数所在线程及C++子线程调用Java函数
//native func
public native void nativeCreateThread();
//jni func/c++ thread -> java
public void notifyActive(){
if(Looper.getMainLooper() == Looper.myLooper()){
Log.d("JavaLog", "notifyActive: 主线程!");
}else{
Log.d("JavaLog", "notifyActive: 子线程!");
}
}
...
#include <jni.h>
#include <string>
#include <pthread.h>
#include <thread>
// 日志输出
#include <android/log.h>
#define TAG "NDKLog"
// __VA_ARGS__ 代表...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);
JavaVM* javaVM = nullptr;
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* javaVm,void* param){
//保存:jvm指针
::javaVM = javaVm;
//获取JNIEnv
JNIEnv* env = nullptr;
jint jRet = javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if(jRet != JNI_OK){
return -1;
}
//返回版本
return JNI_VERSION_1_6;
}
struct Context{
JNIEnv* jEnv;
jobject jobj;
};
Context* ctx = nullptr;
void* threadCallbackFunc1(void* args){
LOGD("threadCallbackFunc1 run!")
/*一,调用notifyActive【C++ thread】*/
/* 需要参数:
* env
* jobject
* */
Context* ctx = (Context*)args;
//env不能使用其它线程的env,必须通过JavaVM去获取
int ret = javaVM->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&ctx->jEnv), nullptr);
if(ret){
LOGD("AttachCurrentThread ERR!");
return nullptr;
}
jclass mainActivityCls = ctx->jEnv->GetObjectClass(ctx->jobj);
jmethodID notifyMethodId = ctx->jEnv->GetMethodID(mainActivityCls,"notifyActive","()V");
ctx->jEnv->CallVoidMethod(ctx->jobj,notifyMethodId);
javaVM->DetachCurrentThread();
LOGD("threadCallbackFunc1 end!")
return nullptr;
}
void* threadCallbackFunc2(){
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndk_1study_1thread_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1thread_MainActivity_nativeCreateThread(
JNIEnv *env,
jobject thiz) {
/*一,调用notifyActive【JNI Func】*/
jclass mainActivityCls = env->GetObjectClass(thiz);
jmethodID notifyMethodId = env->GetMethodID(mainActivityCls,"notifyActive","()V");
env->CallVoidMethod(thiz,notifyMethodId);
env->DeleteLocalRef(mainActivityCls);
/*
* int pthread_create(
* pthread_t* __pthread_ptr, 参数1:pthread_t->long->线程id
* pthread_attr_t const* __attr, 参数2:线程参数集 -> nullptr
* void* (*__start_routine)(void*), 参数3:线程处理函数 -> CPU调度时 -> 执行这个线程处理函数
* void*); 参数4:线程传递参数 -> 线程处理函数 -> 参数
* */
/*错误使用方式:
* env是和线程绑定的,只能本线程使用,不可传递给其它线程去使用
* jobject属于局部引用,函数执行结束,就会被释放,必须提高成全局引用 jobject不与线程绑定
* */
if(!ctx){
ctx = new Context;
//ctx->jEnv = env; 需要通过jvm去获取,线程之间不能传递使用
//ctx->jobj = thiz; 需要提供成全局引用
ctx->jobj = env->NewGlobalRef(thiz);
}
pthread_t pId;
int ret = pthread_create(&pId, nullptr,threadCallbackFunc1, ctx);
if(ret != 0){
LOGD("pthread_create err!");
return;
}
/* //C++11 thread
std::thread td(threadCallbackFunc2);
td.join();//阻塞执行
td.detach();//分离执行*/
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1thread_MainActivity_nativeCloseThread(
JNIEnv *env,
jobject thiz) {
env->DeleteGlobalRef(ctx->jobj);
delete ctx;ctx = nullptr;
}
总结:
- JNI函数运行在主线程
- JNIEnv与线程绑定,线程之间不可传递使用
- jobject需要提升全局引用在子线程中使用
深入探究
通过多种线程的方式来分别打印JavaVM,JNIEnv,jobject来分析其特点!
总结:
- JNIEnv是线程级别的,每个线程都有自己独有的JNIEnv【我们在打印地址时一定要注意哈,有时候可能看到多个子线程打印地址一样呀,怎么回事,这个时候你就要明白线程的执行结束时序,在线程同时存在时,每个线程的地址一定是独有的,不会重复,但是多个线程非同时存在就会看到打印地址时,有的子线程JNIEnv地址一样呀,不要差异】
- JavaVM是进程级别的,每个app都是一个进程,每个进程都有一个JavaVM,所有线程的JavaVM都是相同的!